在学习桥接模式之前,需要学习设计模式中的另外一个原则 — 合成/聚合复用原则
1. 合成/聚合复用原则
合成/聚合复用原则(CARP),尽量使用合成/聚合,尽量不要使用类继承。
合成和聚合都是关联的特殊种类。
- 聚合
是一种弱的“拥有”关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分 - 合成
是一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的周期是一样的
举个例子,大雁有两个翅膀,大雁和翅膀的生命周期是一样的,他们是整体与部分的关系,所以大雁和翅膀是合成关系。
因为大雁是群居动物,所以每只大雁都属于一个雁群,一个雁群可以有多只大雁,所以大雁和雁群是聚合关系。
合成/聚合复用原则的好处就是,优先使用对象的合成/聚合将有助于你保持每个类被封装, 并被集中在单个任务上。这样类和类继承层次会保持较小的规模,并且不太可能增长为不可控制的庞然大物。
继承本身就是一个强耦合的关系,子类必须依赖父类的服务,如果父类发生了变化,那么子类就必须发生变化。我们在抽象问题的过程中,可能会把问题本身抽象成一个父类,让子类去实现父类。它更适用于在 "is-a"关系中,但是如果任何事情都要使用继承去解决,那么父类就会变成真正的“超”类,它要装下任何东西了。
2. 桥接模式的概念和UML图
桥接模式(Bridge),将抽象部分与它的实现部分分离,使他们都可以独立地变化。
这并不是说,让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。
从模式上的定义中我们大致可以了解到,这里“桥梁”的作用其实就是连接**“抽象”与“实现部分”,但事实上,任何多维度变化类或者说多个树状类之间的耦合**都可以使用桥接模式来实现解耦。
如果一个系统需要在构件的抽象化橘色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,可以通过桥接模式使它们在抽象层建立一个关联关系。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,也可以考虑使用桥接模式。
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
来看下桥接模式的UML图:
Abstraction
抽象部分
该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现,该类一般为抽象类RefinedAbstraction
优化的抽象部分
抽象部分的具体实现,该类一般是对抽象部分的方法进行完善和扩展Implementor
实现部分
可以作为借口或抽象类,其方法不一定要与抽象部分中的一致,一致情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法ConcreteImplementorA/ConcreteImplementorB
实现部分的具体实现
完善实现部分中方法定义的具体逻辑
来看下他们的代码:
Implementor
代码是实现部分的抽象类:
public interface Implementor {
/**
* 实现抽象部分的具体方法
*/
void operationImpl();
}
开始具体的实现:
class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("A 实现");
}
}
class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println("B 实现");
}
}
接下来是抽象部分:
public abstract class Abstraction {
// 持有一个实现部分的对象
private Implementor mImplementor;
/**
* 通过实现部分对象的引用构造抽象部分的对象
* @param implementor 实现部分对象的引用
*/
public Abstraction(Implementor implementor) {
this.mImplementor = implementor;
}
/**
* 通过调用实现部分的具体的方法实现具体的功能
*/
public void operation(){
mImplementor.operationImpl();
}
}
抽象部分还有其子类,也可以没有,它主要作用是扩展父类的实现方法:
class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
/**
* 对父类抽象部分中的方法进行扩展
*/
public void refinedOperation() {
// 对Abstraction中的方法进行扩展
}
}
3. 桥接模式的应用
提到桥接模式在Andorid中的应用,就不得不提到 WindowManagerService
,具体参考《Android进阶解密》关于WindowManagerService的源码解析,这里就不再深入源码了,来看下结构吧,我们来看下 WindowManger家族:
PhoneWindow
继承自Window
,Window
通过setWindowManager()
和WindowManager
产生关联。
WindowManager继承自接口 ViewManager
,WindowManagerImpl
是 WindowManager接口的实现类。但是具体功能都会委托给WindowManagerGlobal
来实现。套用了桥接模式,那么这里Window其实就是Abstraction类,而WindowManager就是实现部分的Implementor类了,其他的一目了然。
这里来讲下我是如何理解这里的桥接模式的:
- Window本身
我们知道Window是多种多样的,它至少分为三种:系统级Window(DecorView)、应用级Window(Activity)、子系统级Window(Dialog),而每种的Window它甚至包括多个不同的实现,比如说系统级还有像TitleBar、ActionBar等。 - Window操作
而Window在界面上的操作就是add
、remove
、update
三种,可能还会有其他的扩展
如果让你来实现这些关系,在没有学习桥接模式之前,你会怎么做?
我只想到抽象和派生的形式来做,那么我的实现会可能是这样的:
这时候这个结构其实设计的很不好了,它有几个非常大的问题:
- 紧耦合、扩展性能差
所有子Window的实现方法都依赖父接口和抽象父类,如果抽象父类添加了方法,所有子类都要进行修改,这不符合开放-封闭原则
- 每个子Window代码会变多
加入我加一个子Window,那么我必须要在这个类去实现所有的方法,如果我新加的ConcreteWIndowD
它在add和remove上是和ConcreteWindowA
一样,而在 update上和ConcreteWindowC
一样,那我得写很多重复的代码。
当学习桥接模式后,我们可以把他们分成两个部分,抽象部分和实现部分。
- 抽象部分由Window和其所有实现类构成,他们不做任何操作上的需求,只有 onDraw、onMeasure等这些既定的方法。
- 实现部分就是对所有Window进行管理,通过它们的层级给他们排好序,然后依次的去执行他们的 onDraw、onMeasure方法,即 自己实现的add、remove、update方法。
这个时候就形成最上面的家族图中的结构了。
4. 小结
桥接模式就可以应用到许多开发中,但是它应用得却不多,一个重要的原因是,对于抽象与实现的分离的把握,是不是需要分离、如何分离?对设计者来说要有一个恰到好处的分寸。不管怎么说,桥接模式的优点我们毋庸置疑,它分离抽象和实现、灵活的扩展以及对客户来说透明的实现等。但是使用桥接模式也有一个不明显的缺点,就是不容易设计,对开发者来说要有一定的经验。因此,对桥接模式应用来说,理解很简单,设计却不容易。