简单理解桥接模式
在正式介绍桥接模式之前,我先跟大家谈谈两种常见文具的区别,它们是毛笔和蜡笔。假如我们需要大中小3种型号的画笔,能够绘制12种不同的颜色,如果使用蜡笔,需要准备3×12 = 36支,但如果使用毛笔的话,只需要提供3种型号的毛笔,外加12个颜料盒即可,涉及到的对象个数仅为 3 + 12 = 15,远小于36,却能实现与36支蜡笔同样的功能。如果增加一种新型号的画笔,并且也需要具有12种颜色,对应的蜡笔需增加12支,而毛笔只需增加一支。为什么会这样呢?通过分析我们可以得知:在蜡笔中,颜色和型号两个不同的变化维度(即两个不同的变化原因)融合在一起,无论是对颜色进行扩展还是对型号进行扩展都势必会影响另一个维度;但在毛笔中,颜色和型号实现了分离,增加新的颜色或者型号对另一方都没有任何影响。如果使用软件工程中的术语,我们可以认为在蜡笔中颜色和型号之间存在较强的耦合性,而毛笔很好地将二者解耦,使用起来非常灵活,扩展也更为方便。
如图,上面的例子中有两个变化的维度,毛笔型号和颜色
在软件开发中,我们也提供了一种设计模式来处理与画笔类似的具有多变化维度的情况,即今天将要介绍的桥接模式。
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interfce)模式。
使用场景
桥我们大家都熟悉,顾名思义就是用来将河的两岸联系起来的。而此处的桥是用来将两个独立的结构联系起来,而这两个被联系起来的结构可以独立的变化,所有其他的理解只要建立在这个层面上就会比较容易。
下面是一些官方的说明:
- 如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- “抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
- 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
结构
- Abstraction:定义抽象接口,保存一个对实现化对象的引用
- RefinedAbstraction:扩展Abstraction中的接口定义,改变和修正父类对抽象化的定义
- Implementor:是具体实现的接口,Implementor和RefinedAbstraction接口并不一定完全一致,实际上这两个接口可以完全不一样Implementor提供具体操作方法,而Abstraction提供更高层次的调用
- ConcreteImplementors:实现Implementor接口,给出具体实现
实现代码
颜色接口(Implementor)
public interface Color {
void touchUp();
}
具体颜色类(ConcreteImplementor)
Red
public class Red implements Color{
@Override
public void touchUp() {
System.out.print("红色");
}
}
Yellow
public class Yellow implements Color{
@Override
public void touchUp() {
System.out.print("黄色");
}
}
Blue
public class Blue implements Color{
@Override
public void touchUp() {
System.out.print("蓝色");
}
}
Green
public class Green implements Color{
@Override
public void touchUp() {
System.out.print("绿色");
}
}
毛笔抽象类(Abstraction)
这里包含了一个Implementor
public abstract class WritingBrush {
protected Color color;
public WritingBrush(Color color) {
this.color = color;
}
public abstract void write();
}
具体毛笔类(RefinedAbstraction)
WritingBrush_L
public class WritingBrush_L extends WritingBrush{
public WritingBrush_L(Color color) {
super(color);
}
@Override
public void write() {
System.out.print("用");
super.color.touchUp();
System.out.print("大号毛笔");
System.out.println("写字");
}
}
WritingBrush_M
public class WritingBrush_M extends WritingBrush{
public WritingBrush_M(Color color) {
super(color);
}
@Override
public void write() {
System.out.print("用");
super.color.touchUp();
System.out.print("中号毛笔");
System.out.println("写字");
}
}
WritingBrush_S
public class WritingBrush_S extends WritingBrush{
public WritingBrush_S(Color color) {
super(color);
}
@Override
public void write() {
System.out.print("用");
super.color.touchUp();
System.out.print("小号毛笔");
System.out.println("写字");
}
}
客户端
Client
public class Client {
public static void main(String[] args) {
WritingBrush writingBrush1 = new WritingBrush_L(new Red());
writingBrush1.write();
WritingBrush writingBrush2 = new WritingBrush_M(new Yellow());
writingBrush2.write();
WritingBrush writingBrush3 = new WritingBrush_S(new Blue());
writingBrush3.write();
}
}
测试结果
用红色大号毛笔写字
用黄色中号毛笔写字
用蓝色小号毛笔写字
桥梁模式的优缺点
优点
- 分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象
- 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数
- 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”
缺点
- 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。