跳至主要內容

桥接模式

AruNi_Lu设计模式设计模式与范式约 1481 字大约 5 分钟

本文内容

前言

桥接模式在日常开发中使用得并不多,而且理解起来也有难度,所以下面只是简单地介绍下桥接模式,以及它怎么运用。

1. 什么是桥接模式

桥接模式 的定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化

这个定义可以说是非常的抽象了,它还有另一种比较简单的理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。通过组合关系来替代继承关系,避免子类过多过杂。

可以发现,桥接模式主要是用于将两个独立的模块,通过组合的方式连接起来使用

2. 桥接模式例子

下面用一个实际生活中的例子来讲解。假设我们是负责给星巴克做订单系统的外包码农,星巴克在一开始时说他们的咖啡只提供正常杯(中杯)、原味和加糖这几种选择。

这个需求还是很简单的,我们先定义一个点咖啡的接口,通过一个下单方法,至于点哪种口味的咖啡,由子类去决定即可:

// 点咖啡的接口
public interface ICoffee {
    void orderCoffee(int count);
}


// 原味咖啡类
public class CoffeeOriginal implements ICoffee {
    @Override
    public void orderCoffee(int count) {
        System.out.println(String.format("原味咖啡%d杯",count));
    }
}

// 加糖咖啡类
public class CoffeeWithSugar implements ICoffee {
    @Override
    public void orderCoffee(int count) {
        System.out.println(String.format("加糖咖啡%d杯",count));
    }
}

过了没多久,星巴克又过来说,他们还要再添加两个规格容量的咖啡,大杯和小杯。

虽然我们之前是面向抽象编程的,但是现在口味的组合就很多了,有小杯的原味和加糖、中杯的原味和加糖、大杯的原味和加糖,这就需要 6 个实现类。而如果后续还有其他需求,比如三分糖、五分糖、七分糖等等。这实现类不得爆炸?

此时,桥接模式就能派上用场了。这里有两个变化维度,咖啡的容量和口味,而且都是可以独立变化的。

桥接模式的设计结构图如下:

桥梁模式所涉及的角色有:

  • 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
  • 修正抽象化(RefinedAbstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
  • 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
  • 具体实现化(ConcreteImplementor)角色:这个角色给出实现化角色接口的具体实现。

该案例的整体设计如下(本例中无需扩展抽象化角色):

image-20230408161433475

代码实现如下:

public interface ISugarDegree {
    void chooseSugarDegree();
}

public class SugarDegreeAsc implements ISugarDegree {
    @Override
    public void chooseSugarDegree() {
        System.out.println("加糖");
    }
}

public class SugarDegreeNormal implements ISugarDegree {
    @Override
    public void chooseSugarDegree() {
        System.out.println("正常糖");
    }
}


public abstract class Coffee {
    // 通过组合的方式将 Coffee 的糖度添加进来
    protected ISugarDegree sugarDegree;

    // 通过依赖注入将 ICoffeeAdditives 注入进来
    public Coffee(ISugarDegree sugarDegree) {
        this.sugarDegree = sugarDegree;
    }

    public abstract void orderCoffee(int count);
}

public class LargeCoffee extends Coffee {
    public LargeCoffee(ISugarDegree sugarDegree) {
        super(sugarDegree);
    }

    @Override
    public void orderCoffee(int count) {
        sugarDegree.chooseSugarDegree();
        System.out.println(String.format("大杯咖啡%d杯", count));
    }
}

public class MediumCoffee extends Coffee {
    public MediumCoffee(ISugarDegree sugarDegree) {
        super(sugarDegree);
    }

    @Override
    public void orderCoffee(int count) {
        sugarDegree.chooseSugarDegree();
        System.out.println(String.format("中杯杯咖啡%d杯", count));
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Coffee largeWithSugarDegreeNormal = new LargeCoffee(new SugarDegreeNormal());
        largeWithSugarDegreeNormal.orderCoffee(3);
    }
}

/* 输出:
正常糖
大杯咖啡3杯
*/

现在我们如果需要添加容量,比如小杯、女神杯等,则直接添加一个类,继承 Coffee 即可。或者添加糖度,比如三分糖、七分糖,也只需要添加一个类,实现 ISugarDegree 接口即可。

不知道你有没有发现,其实 桥接模式就是通过组合的方式,将 M*N 个子类/实现类简化成了 M+N 个,从而大大减少了类的个数

3. 总结

桥接模式的理解和使用是比较困难的,使用的场景也不多,但是还是需要了解一下。

桥接模式的定义有两种不同的理解:

  • 将抽象部分与它的实现部分分离,使它们都可以独立地变化
  • 一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展

第二种方式比较好理解,其实就是类似于 “组合优于继承” 的设计原则,通过 组合的方式来替代继承,从而避免当需求扩充时,导致子类/实现类的爆炸增长。

桥接模式最大的作用无非就是 通过组合的方式,将 M*N 个子类/实现类简化成了 M+N 个,从而大大减少了类的个数

4. 参考文章

上次编辑于: