初探桥接模式

代码链接

koonchen/design-patterns/bridge

要点

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

假设有一个抽象有多种实现需要用继承来协调,但是继承机制将抽象与它的实现固定在一起,难以对抽象和实现独立地进行修改、扩充、重用。

现在 Window 上有一个抽象,我们可以将它移植到 X Window SystemPresentation Manager 上,首先哟啊做的是实现窗口,我们分别用 XWindowPMWindow 实现两种系统的窗口,但是这样做有两个不足之处:

  • 假设存在的抽象在 Window 下是 IconWindow 且用来处理图标,我们在另外两个系统下完成 XIconWindowPMIconWindow 实现处理图标,加上窗口类,我们不得不为每一种类型的窗口都定义两个类,而为了支持第三个系统,我们还必须为每一种窗口定义一个新的 Window 子类,过于繁琐,如图:

cover

  • 如果我们不创建两个类又会产生怎样的问题?继承机制使得客户代码与平台相关。比如 XWindow 对象将 Window 抽象和 X Window 的实现绑定在一起,使得很难将这些代码移植到其他平台上,所以客户在创建窗口时不应该涉及到具体实现。

bridge 模式解决上面问题的方式是,将 Window 抽象和它的实现分别放到独立的类层次结构中。其中一个类层次结构针对窗口接口,比如: IconWindow 等,他们会在大量的系统中被使用;另一个类层次针对平台相关的窗口实现,其根类定为 WindowImp ,其子类用来实现平台的窗口实现。如下图结构所示:

cover

Window 子类的操作使用的都是 WindowImp 接口的抽象实现的,因此存在聚合关系,与此同时,窗口抽象和平台相关的实现成功进行了分离。我们将 WindowWindowImp 之间的关系称作 桥接

什么时候适合这种模式:

  • 不希望在抽象和实现之间有一个固定的绑定。
  • 类的抽象以及它的实现都可以通过生成子类的方法进行扩充。
  • 对抽象的实现部分修改希望对客户不产生影响。
  • 希望对客户完全隐藏抽象的实现部分。
  • 如图一中那样,有许多类要生成,我们必须将一个对象分解成两个部分,这种情况叫『嵌套的普化』。
  • 希望在多个对象间共享实现,但同时客户并不知道这一点。

他拥有哪些参与者:

  • Abstraction ( Window ) — 定义抽象类的接口。
  • RefinedAbstraction ( IconWindow ) — 扩充由 Abstraction 定义的接口。
  • Implementor ( WIndowImp ) — 定义实现类接口,一般该接口仅提供基本操作。
  • ConcreteImplementor ( XWindowImp ) — 实现 Implementor 接口。

现在, bridge 模式的结构就呼之欲出了:

cover

bridge 模式的优点主要体现在:

  • 分离接口及其实现部分:一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。同时,接口与实现分离有助于分层。
  • 提高可扩充性:可以独立地对 AbstractionImplementor 层次进行扩充。
  • 实现细节对客户透明:可以对客户隐藏实现细节。

实现

在实现 bridge 模式前,有几点需要注意的内容:

  • 仅有一个 Implementor :在只有一个实现的时候,没有必要创建一个抽象的 Implementor 类。这是 bridge 模式的退化情况;在 AbstractionImplementor 之间有一种一对一的关系。尽管如此,当你希望改变一个类的实现不会影响已有的客户程序的时候,模式的分离机制还是非常有用的,不必重新编译它们,仅需重新连接即可。在 C++ 中, Implementor 类的类接口可以在一个私有的头文件中定义,这个文件不提供给客户,这样你就对客户彻底隐藏了一个类的实现。
  • 创建正确的 Implementor 对象:当存在多个 Implementor 类时,你应该用何种方法,在何时何处确定创建哪一个 Implementor 类呢?如果 Abstraction 知道所有的 ConcreteImplementor 类,它就可以在它的构造器中对其中一个类进行实例化;或者我们可以首先选择一个缺省的实现,然后根据需求改变这个实现;也可以代理给另一个对象,由它一次决定。
  • 共享 Implementor 对象: C++ 中常用 Handle/Body 方法在多个对象间共享一些实现,包括 C++ 在内一般是使用引用计数器,需要维护指向 Implementor 的指针。
  • 采用多重继承机制:假设我们在 C++ 中用 pulic 继承 Abstraction ,而以 private 继承 ConcreteImplementor ,我们知道这种方法依赖于静态继承,它将实现部分与接口固定不变地绑定在一起。因此在 C++ 中不能用多重继承来实现真正的 bridge 模式。

java 实现

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
39
40
41
42
43
44
45
46
class Abstraction {
Implementor implementor;
void operation() {
implementor.operationImp();
}
}

class Implementor {
void operationImp() {
System.out.println("Implementor instance has been created");
}
}

class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(String classname) {

try {
implementor = (Implementor) Class.forName(classname).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}

class ConcreteImplementorA extends Implementor {
@Override
void operationImp() {
System.out.println("ConcreteImplementorA instance has been created");
}
}

class ConcreteImplementorB extends Implementor {
@Override
void operationImp() {
System.out.println("ConcreteImplementorB instance has been created");
}
}

public class BridgeClient {
public static void main(String[] args) {
Abstraction abstraction = new RefinedAbstraction(ConcreteImplementorA.class.toString().substring(6));
abstraction.operation();
abstraction = new RefinedAbstraction((ConcreteImplementorB.class.toString().substring(6)));
abstraction.operation();
}
}

主流实现也大多异曲同工,或者改用接口的方式,在 Implementor 下定义通用的方法抽象,在 Abstraction 下定义使用者的抽象,因为抽象和实现进行了分离,所以可以更加灵活实现其他功能。

python 实现

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
class Implementor(object):
def operationImp(self):
print('Implementor instance has been created')

class Abstraction(object):
def operation(self):
self._imp.operationImp()

class ConcreteImplementorA(Implementor):
def operationImp(self):
print('ConcreteImplementorA instance has been created')

class ConcreteImplementorB(Implementor):
def operationImp(self):
print('ConcreteImplementorB instance has been created')

class RefinedAbstraction(Abstraction):
def __init__(self, cls):
self._imp = cls()

if __name__ == '__main__':
abstraction = RefinedAbstraction(ConcreteImplementorA)
abstraction.operation()
abstraction = RefinedAbstraction(ConcreteImplementorB)
abstraction.operation()

在 python 下,我们可以将 ConcreteImplementor 作为参数在 RefinedAbstraction 中使用,实现与上例大同小异,所以不再复述实现了。

bridge 模式的重点在于将抽象和实现分离,这种思想的有趣之处在于消除了接口和实现的绑定,我们更加灵活地对实现增删改。

bridge 模式可以被 abstract factory 模式实现,把 factory 的子类作为 ConcreteImplementor ,把具体的 RefinedAbstraction 作为 product 的子类,可以解释为:特定的产品用特定的工厂实现来生产。但也存在不同,抽象工厂的主要目的是生产产品,重点是创建;桥接模式的主要目的是换一个方式使用实现,重点是使用。

adapter 模式用来帮助无关的类进行协同工作,它通常在系统设计完成才会被使用; bridge 模式则在系统开始就可以被使用。