初探桥接模式
代码链接
koonchen/design-patterns/bridge
要点
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
假设有一个抽象有多种实现需要用继承来协调,但是继承机制将抽象与它的实现固定在一起,难以对抽象和实现独立地进行修改、扩充、重用。
现在 Window
上有一个抽象,我们可以将它移植到 X Window System
和 Presentation Manager
上,首先哟啊做的是实现窗口,我们分别用 XWindow
和 PMWindow
实现两种系统的窗口,但是这样做有两个不足之处:
- 假设存在的抽象在
Window
下是IconWindow
且用来处理图标,我们在另外两个系统下完成XIconWindow
和PMIconWindow
实现处理图标,加上窗口类,我们不得不为每一种类型的窗口都定义两个类,而为了支持第三个系统,我们还必须为每一种窗口定义一个新的Window
子类,过于繁琐,如图:
- 如果我们不创建两个类又会产生怎样的问题?继承机制使得客户代码与平台相关。比如
XWindow
对象将Window
抽象和X Window
的实现绑定在一起,使得很难将这些代码移植到其他平台上,所以客户在创建窗口时不应该涉及到具体实现。
bridge 模式解决上面问题的方式是,将 Window
抽象和它的实现分别放到独立的类层次结构中。其中一个类层次结构针对窗口接口,比如: IconWindow
等,他们会在大量的系统中被使用;另一个类层次针对平台相关的窗口实现,其根类定为 WindowImp
,其子类用来实现平台的窗口实现。如下图结构所示:
Window
子类的操作使用的都是 WindowImp
接口的抽象实现的,因此存在聚合关系,与此同时,窗口抽象和平台相关的实现成功进行了分离。我们将 Window
和 WindowImp
之间的关系称作 桥接 。
什么时候适合这种模式:
- 不希望在抽象和实现之间有一个固定的绑定。
- 类的抽象以及它的实现都可以通过生成子类的方法进行扩充。
- 对抽象的实现部分修改希望对客户不产生影响。
- 希望对客户完全隐藏抽象的实现部分。
- 如图一中那样,有许多类要生成,我们必须将一个对象分解成两个部分,这种情况叫『嵌套的普化』。
- 希望在多个对象间共享实现,但同时客户并不知道这一点。
他拥有哪些参与者:
- Abstraction ( Window ) — 定义抽象类的接口。
- RefinedAbstraction ( IconWindow ) — 扩充由 Abstraction 定义的接口。
- Implementor ( WIndowImp ) — 定义实现类接口,一般该接口仅提供基本操作。
- ConcreteImplementor ( XWindowImp ) — 实现 Implementor 接口。
现在, bridge 模式的结构就呼之欲出了:
bridge 模式的优点主要体现在:
- 分离接口及其实现部分:一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。同时,接口与实现分离有助于分层。
- 提高可扩充性:可以独立地对
Abstraction
和Implementor
层次进行扩充。- 实现细节对客户透明:可以对客户隐藏实现细节。
实现
在实现 bridge 模式前,有几点需要注意的内容:
- 仅有一个
Implementor
:在只有一个实现的时候,没有必要创建一个抽象的Implementor
类。这是 bridge 模式的退化情况;在Abstraction
与Implementor
之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序的时候,模式的分离机制还是非常有用的,不必重新编译它们,仅需重新连接即可。在 C++ 中,Implementor
类的类接口可以在一个私有的头文件中定义,这个文件不提供给客户,这样你就对客户彻底隐藏了一个类的实现。- 创建正确的
Implementor
对象:当存在多个Implementor
类时,你应该用何种方法,在何时何处确定创建哪一个Implementor
类呢?如果Abstraction
知道所有的ConcreteImplementor
类,它就可以在它的构造器中对其中一个类进行实例化;或者我们可以首先选择一个缺省的实现,然后根据需求改变这个实现;也可以代理给另一个对象,由它一次决定。- 共享
Implementor
对象: C++ 中常用 Handle/Body 方法在多个对象间共享一些实现,包括 C++ 在内一般是使用引用计数器,需要维护指向Implementor
的指针。- 采用多重继承机制:假设我们在 C++ 中用 pulic 继承
Abstraction
,而以 private 继承ConcreteImplementor
,我们知道这种方法依赖于静态继承,它将实现部分与接口固定不变地绑定在一起。因此在 C++ 中不能用多重继承来实现真正的 bridge 模式。
java 实现
1 | class Abstraction { |
主流实现也大多异曲同工,或者改用接口的方式,在 Implementor
下定义通用的方法抽象,在 Abstraction
下定义使用者的抽象,因为抽象和实现进行了分离,所以可以更加灵活实现其他功能。
python 实现
1 | class Implementor(object): |
在 python 下,我们可以将 ConcreteImplementor
作为参数在 RefinedAbstraction
中使用,实现与上例大同小异,所以不再复述实现了。
bridge 模式的重点在于将抽象和实现分离,这种思想的有趣之处在于消除了接口和实现的绑定,我们更加灵活地对实现增删改。
bridge 模式可以被 abstract factory 模式实现,把 factory
的子类作为 ConcreteImplementor
,把具体的 RefinedAbstraction
作为 product
的子类,可以解释为:特定的产品用特定的工厂实现来生产。但也存在不同,抽象工厂的主要目的是生产产品,重点是创建;桥接模式的主要目的是换一个方式使用实现,重点是使用。
adapter 模式用来帮助无关的类进行协同工作,它通常在系统设计完成才会被使用; bridge 模式则在系统开始就可以被使用。