桥接模式介绍
桥接模式(Bridge Pattern)也称为桥梁模式,是结构型设计模式之一。顾名思义其与现实中的桥梁作用相同,有连接两岸的作用。
桥接模式定义
将抽象部分和实现部分分离,使它们都可以独立地进行变化。
桥接模式使用场景
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
桥接模式 UML 图
角色介绍:
- Abstraction:抽象部分。该类保持了一个对象实现部分的引用,该类或者继承类可通过调用实现部分对象的方法来完成一些功能。
- RefinedAbstraction:优化的抽象部分。该类是对抽象部分的方法进行完善和扩展。
- Implementor:实现部分。定义一类功能的基本操作。
- ConcreteImplementor:具体实现部分。
桥接模式实现
现实生活中桥接模式的应用很多,比如喝咖啡,杯子大小可以分为大杯、小杯,甜度可以分为加糖、不加糖,杯子大小与加糖与否是独立变化的,但对于咖啡两者又存在联系;再比如笔,粗细可以分为0.7mm 笔头,0.5mm笔头,颜色可以分为红色、黑色,两者彼此独立,也存在联系。
看完例子可能并不能体会到桥接模式,下面就笔的生产为例,笔头有0.7mm、有0.5mm笔头,暂且我们以大、小来区分,我们设计的类结构如下:
没毛病,这时假如我们要生产两种颜色的笔,分别为黑色、红色。这时比较直接的设计如下:
看似没毛病,细思极恐,这才是项目初期,假如我们笔的粗细还增加了0.3mm,0.25mm等,颜色增加到了 5 种颜色;再假如我们除了支持笔的粗细、笔的颜色、还支持带笔帽、或者为自动笔等,这样显著问题就是类的数量爆棚,另一个问题就是牵一发而动全身。对象的继承关系实在编译就定义好了,所以无法运行时改变从父类继承的实现。子类的实现与它的父类就非常紧密的依赖,以至于父类实现中的任何变化都会导致子类的变化。这种依赖关系限制了灵活性并最终限制了复用性。
笔的大小与颜色变化独立,属于两个维度,我们可以将其分离,重新设计如下:
这样笔的大小和颜色就分离开来,两者可以独立变化,笔支持持有了颜色的引用,在这里是聚合的关系,当需要生产黑色 0.77mm笔时,只需要在 BigPen 中通过Color的应用设置颜色即可。这种就是桥接模式。
抽象部分(Abstraction)
public abstract class Pen {
protected Color color;
public Pen(Color color) {
this.color = color;
}
public abstract void draw();
}
优化的抽象部分(RefinedAbstraction)
public class BigPen extends Pen {
public BigPen(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("0.7mm的" + color.makeColor() + "笔");
}
}
public class SmallPen extends Pen {
public SmallPen(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("0.5mm的" + color.makeColor() + "笔");
}
}
实现部分
public interface Color {
String makeColor();
}
具体的实现部分
public class BlackColor implements Color {
@Override
public String makeColor() {
return "黑色";
}
}
public class RedColor implements Color {
@Override
public String makeColor() {
return "红色";
}
}
客户端
public class Client {
public static void main(String[] args) {
Color redColor = new RedColor();
Color blackColor = new BlackColor();
Pen pen1 = new BigPen(redColor);
Pen pen2 = new BigPen(blackColor);
Pen pen3 = new SmallPen(redColor);
Pen pen4 = new SmallPen(blackColor);
pen1.draw();
pen2.draw();
pen3.draw();
pen4.draw();
}
}
运行结果:
0.7mm的红色笔
0.7mm的黑色笔
0.5mm的红色笔
0.5mm的黑色笔
如果需要增加是否带笔帽,只需新建接口,在 Pen 中传入即可,只增加必要的类,并且每个维度可以独立变化,也符合了开放-封闭原则。
总结
优先使用对象的组合、聚合有助于保持每个类的封装,并被集中在单个任务上。这样类和类继承层次可以保持较小规模。
优点
1.分离抽象接口及其实现部分。提高了比继承更好的解决方案。
2.桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
3.实现细节对客户透明,客户无需关心实现细节,它已经由抽象层通过聚合关系完成了封装。
注意事项
桥接模式是非常简单的,使用该模式时主要考虑如何拆分抽象和实现,并不是一涉及继承就要考虑使用该模式,那还要继承干什么呢?桥接模式的意图还是对变化的封装,尽量把可能变化的因素封装到最细、最小的逻辑单元中,避免风险扩散。因此读者在进行系统设计时,发现类的继承有N层时,可以考虑使用桥接模式。
Android 源码中的桥接模式
在应用层,对于普通控件、View 和 Canvas 就可以看做桥接模式,View 为抽象角色,Canvas 为实现角色。
在 Framwork 内部的源码中,比较经典的就是 Window 、WindowManager 和 WindowManagerService 之间的关系,如下图:
- Window 和 PhoneWindow 构成了窗口的抽象部分,Window 为抽象接口,PhoneWindow 为抽象部分的具体实现及扩展。
- WindowManager 为实现部分的实现接口,WindowManagerImpl 为实现部分的具体逻辑实现。
这就是桥接模式。其中 WindowManagerImpl 使用 WindowManagerGlobal 对象,通过 IWindowManager 接口与 WindowManagerService 进行交互,并有 WMS 完成具体的窗口管理工作。
Window 和 WindowManager 的桥梁搭建的主要代码如下:
Window.java
public abstract class Window {
private WindowManager mWindowManager;
...
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManager getWindowManager() {
return mWindowManager;
}
}