0%

GoF设计模式(九):Bridge Pattern 桥接模式

有时候一个类,可能会拥有多个变化维度。比如奶茶可以选择容量大小、口味风味。容易想到的实现方案是通过继承实现各种组合,但是这样会很容易造成类爆炸。那么有没有什么良策呢?答案就是 Bridge Pattern 桥接模式,其是结构性模式的一种,本文就让我们来了解下该模式

abstract.jpeg

模式思想

这里,我们需要构建一个汽车类,我们知道汽车有很多品牌,比如宝马BMW、奔驰Benz。与此同时,汽车的颜色又是五颜六色的,常见的有红、绿、蓝。可以看到在这里对于汽车而言,其存在两个维度——品牌、颜色。前者有2种变化:宝马、奔驰;后者有3种选择:红、绿、蓝。如果使用继承的方式实现各品牌不同颜色的汽车,那将会产生2x3=6个具体的汽车类,示意图如下所示。如果以后需要增加玛莎拉蒂、劳斯莱斯品牌,再新增一个汽车空间大小的维度。可以想见各种组合下的汽车类数目将会激增,即所谓的类爆炸

figure 1.jpeg

既然直接通过继承的方式不妥,那么我们就走另外一条路——Bridge Pattern 桥接模式。这里我们先看下使用桥接模式后的示意图,此举是为了给大家建立一个直观的感受。可以看到此时只有2+3=5个具体类。特别是在维度数量、每个维度可选择数目较大时,桥接模式可以大大减少类的数量

figure 2.jpeg

可以看到,通过桥接模式我们将两个变化的维度(品牌、颜色)进行了拆分。汽车类通过继承只负责单个维度的变化——即品牌,而另外一个维度的变化(颜色)则是通过组合的方式实现被不同品牌的汽车所使用。现在我们可以看到,两个不同维度是通过各自独立的继承实现体系进行建立。此举一方面很好的体现了类的单一职责原则;另一方面也很好的应用了开闭原则。与此同时,相比较通过静态的继承方式而言,利用动态组合的方式实现了解耦更加灵活。现在就让我们来正式介绍下桥接模式,其通常含有四个角色:

  • 抽象化角色:其定义了类的共有方法接口。由于在Java的接口中不可以定义实例变量,故Java中通常是使用抽象类而不是接口。即这里的Car汽车抽象类
  • 修正抽象化角色:其通过继承抽象化角色的方式实现了某个维度的变化(一般选择更符合该类本质的维度,比如对这里汽车而言,颜色、品牌这两个维度,显然品牌更符合汽车的本质),通常情况下它们是具体类。比如这里我们通过继承Car类来实现不同品牌的汽车——宝马BMW、奔驰Benz类
  • 实现化角色:前面我们说到,汽车有两个维度的变化,我们通过抽象化角色-修正抽象化角色这条继承体系实现了关于品牌维度的变化。那颜色这个维度,怎么办呢?其实很简单,我们再定义一个关于Color颜色的接口即可,其就是实现化角色。这里的实现化可以理解为其是对某个维度变化的实现。这样抽象化角色就可以通过持有一个Color接口的实例以拥有具备该维度变化的能力,这也是该模式名称桥接的由来,即通过组合桥接的方式让Car与Color这两个独立的继承实现体系之间发生联系
  • 具体实现化角色:其是实现化角色的具体实现类,比如这里的Red、Green、Blue类

实现

现在让我们来通过Java具体地实现这个例子。首先,我们定义抽象化角色——即Car汽车类,可以看到其内部会持有其它维度接口的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 抽象化角色: 汽车
*/
public abstract class Car {
// 通过组合来桥接其它的行为/维度
protected Color color;

public void setColor(Color color) {
this.color = color;
}

abstract public void run();
}

然后,我们通过继承该Car类来实现不同品牌的汽车,即所谓的抽象化角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 修正抽象化角色: Benz 奔驰
*/
public class Benz extends Car {
public Benz(Color color) {
super.setColor(color);
}

@Override
public void run() {
color.useColor();
System.out.println("Benz奔驰在高速上狂奔 ... ");
}
}
...
/**
* 修正抽象化角色: BMW 宝马
*/
public class BMW extends Car{
public BMW(Color color) {
super.setColor(color);
}

@Override
public void run() {
color.useColor();
System.out.println("BMW宝马在路上缓慢行驶 ...");
}
}

解决了品牌维度后,我们就可以通过实现化角色-具体实现化角色这条继承实现体系来实现颜色这个维度了。这里,我们先通过定义该维度的接口——即实现化角色Color接口

1
2
3
4
5
6
7
8
9
/**
* 实现化角色: 颜色
*/
public interface Color {
/**
* 使用某种颜色
*/
void useColor();
}

然后我们通过具体实现化角色来实现具体的颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 具体实现化角色: 红色
*/
public class Red implements Color{
/**
* 使用红色
*/
@Override
public void useColor() {
System.out.print("红色的");
}
}
...
/**
* 具体实现化角色: 绿色
*/
public class Green implements Color{
/**
* 使用绿色
*/
@Override
public void useColor() {
System.out.print("绿色的");
}
}
...
/**
* 具体实现化角色: 蓝色
*/
public class Blue implements Color{
/**
* 使用蓝色
*/
@Override
public void useColor() {
System.out.print("蓝色的");
}
}

现在桥接模式就已经被我们完成了,让我们来看看client如果使用它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Bridge Pattern 桥接模式Demo
*/
public class BridgePatternDemo {
public static void main(String[] args) {
// 绿色宝马
BMW greenBmw = new BMW( new Green() );
greenBmw.run();

// 蓝色宝马
BMW blueBmw = new BMW( new Blue() );
blueBmw.run();

// 红色奔驰
Benz redBenz = new Benz( new Red() );
redBenz.run();
}
}

测试结果如下,符合预期

figure 3.png

现在,相信大家对桥接模式应该有一个很好的理解了。这里我们只选用了两个维度进行展示,实际上对于多个维度桥接模式也是特别合适的。无非就是通过建立多个实现化角色-具体实现化角色继承实现体系来分别实现各个维度的功能,最后通过桥接实现有机地组合

参考文献

  1. Head First 设计模式 弗里曼著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝