意图

桥接模式是一种结构型模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用。

问题

假如你有一个几何形状Shape类,从它能扩展出两个子类:圆形Circle方形Square。你希望对这样的类层次结构进行扩展以使其包含颜色,所以你打算创建名为红色Red蓝色Blue的形状子类。但是,由于你已有两个子类,所以总共需要创建四个类才能覆盖所有组合,例如蓝色圆形BlueCircle红色方形RedSquare

所有组合类的数量将以几何级数增长

在层次结构中新增形状和颜色将导致代码复杂程度指数增长。例如添加三角形状,你需要新增两个子类,也就是每种颜色一个;此后新增一种新颜色需要新增三个子类,即每种形状一个。如此以往,情况会越来越糟糕。

解决方案

问题的根本原因是我们试图在两个独立的维度——形状与颜色——上扩展形状类。这在处理类继承时是很常见的问题。

桥接模式通过将继承改为组合的方式来解决这个问题。具体来说,就是抽取其中一个维度并使之成为独立的类层次,这样就可以在初始类中引用这个新层次的对象,从而使得一个类不必拥有所有的状态和行为。

将一个类层次转化为多个相关的类层次,避免单个类层次的失控

根据该方法,我们可以将颜色相关的代码抽取到拥有红色蓝色两个子类的颜色类中,然后在形状类中添加一个指向某一颜色对象的引用成员变量。现在,形状类可以将所有与颜色相关的工作委派给连入的颜色对象。这样的引用就成为了形状颜色之间的桥梁。此后,新增颜色将不再需要修改形状的类层次,反之亦然。

抽象部分和实现部分

抽象部分(也被称为接口)是一些实体的高阶控制层。该层自身不完成任何具体的工作,它需要将工作委派给实现部分层(也被称为平台)。

注意,这里提到的内容与编程语言中的接口抽象类无关。它们并不是一回事。

在实际的程序中,抽象部分是图形用户界面(GUI),而实现部分则是底层操作系统代码(API),GUI 层调用 API 层来对用户的各种操作做出响应。

一般来说,你可以在两个独立方向上扩展这种应用:

  • 开发多个不同的 GUI(例如面向普通用户和管理员进行分别配置)
  • 支持多个不同的 API(例如,能够在 Windows、Linux 和 macOS 上运行该程序)。

在最糟糕的情况下,程序可能会是一团乱麻,其中包含数百种条件语句,连接着代码各处不同种类的 GUI 和各种 API。

在庞杂的代码中,即使是很小的改动都非常难以完成,因为你必须要在整体上对代码有充分的理解。而在较小且定义明确的模块中,进行修改则要容易得多

你可以将特定接口-平台的组合代码抽取到独立的类中,以在混乱中建立一些秩序。但是,你很快会发现这种类的数量很多。类层次将以指数形式增长,因为每次添加一个新的 GUI 或支持一种新的 API 都需要创建更多的类。

让我们试着用桥接模式来解决这个问题。该模式建议将类拆分为两个类层次结构:

  • 抽象部分:程序的 GUI 层。
  • 实现部分:操作系统的 API。

创建跨平台应用程序的一种方法

抽象对象控制程序的外观,并将真实工作委派给连入的实现对象。不同的实现只要遵循相同的接口就可以互换,使同一 GUI 可在 Windows 和 Linux 下运行。

最后的结果是:你无需改动与 API 相关的类就可以修改 GUI 类。此外如果想支持一个新的操作系统,只需在实现部分层次中创建一个子类即可。

结构

  1. 抽象部分(Abstraction)提供高层控制逻辑,依赖于完成底层实际工作的实现对象。
  2. 实现部分(Implementation)为所有具体实现声明通用接口。抽象部分仅能通过在这里声明的方法与实现对象交互。
    抽象部分可以列出和实现部分一样的方法,但是抽象部分通常声明一些复杂行为,这些行为依赖于多种由实现部分声明的原语操作。
  3. 具体实现(Concrete Implementations)中包括特定于平台的代码。
  4. 精确抽象(Refined Abstraction)提供控制逻辑的变体。与其父类一样,它们通过通用实现接口与不同的实现进行交互。
  5. 通常情况下,客户端(Client)仅关心如何与抽象部分合作。但是,客户端需要将抽象对象与一个实现对象连接起来。

实现方式

  1. 明确类中独立的维度。独立的概念可能是:抽象/平台,域/基础设施,前端/后端或接口/实现。
  2. 了解客户端的业务需求,并在抽象基类中定义它们。
  3. 确定在所有平台上都可执行的业务。并在通用实现接口中声明抽象部分所需的业务。
  4. 为你域内的所有平台创建实现类,但需确保它们遵循实现部分的接口。
  5. 在抽象类中添加指向实现类型的引用成员变量。抽象部分会将大部分工作委派给该成员变量所指向的实现对象。
  6. 如果你的高层逻辑有多个变体,则可通过扩展抽象基类为每个变体创建一个精确抽象。
  7. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。此后,客户端只需与抽象对象进行交互,无需和实现对象打交道。

代码演示

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using System;

namespace RefactoringGuru.DesignPatterns.Bridge.Conceptual
{
// The Abstraction defines the interface for the "control" part of the two
// class hierarchies. It maintains a reference to an object of the
// Implementation hierarchy and delegates all of the real work to this
// object.
class Abstraction
{
protected IImplementation _implementation;

public Abstraction(IImplementation implementation)
{
this._implementation = implementation;
}

public virtual string Operation()
{
return "Abstract: Base operation with:\n" +
_implementation.OperationImplementation();
}
}

// You can extend the Abstraction without changing the Implementation
// classes.
class ExtendedAbstraction : Abstraction
{
public ExtendedAbstraction(IImplementation implementation) : base(implementation)
{
}

public override string Operation()
{
return "ExtendedAbstraction: Extended operation with:\n" +
base._implementation.OperationImplementation();
}
}

// The Implementation defines the interface for all implementation classes.
// It doesn't have to match the Abstraction's interface. In fact, the two
// interfaces can be entirely different. Typically the Implementation
// interface provides only primitive operations, while the Abstraction
// defines higher- level operations based on those primitives.
public interface IImplementation
{
string OperationImplementation();
}

// Each Concrete Implementation corresponds to a specific platform and
// implements the Implementation interface using that platform's API.
class ConcreteImplementationA : IImplementation
{
public string OperationImplementation()
{
return "ConcreteImplementationA: The result in platform A.\n";
}
}

class ConcreteImplementationB : IImplementation
{
public string OperationImplementation()
{
return "ConcreteImplementationA: The result in platform B.\n";
}
}

class Client
{
// Except for the initialization phase, where an Abstraction object gets
// linked with a specific Implementation object, the client code should
// only depend on the Abstraction class. This way the client code can
// support any abstraction-implementation combination.
public void ClientCode(Abstraction abstraction)
{
Console.Write(abstraction.Operation());
}
}

class Program
{
static void Main(string[] args)
{
Client client = new Client();

Abstraction abstraction;
// The client code should be able to work with any pre-configured
// abstraction-implementation combination.
abstraction = new Abstraction(new ConcreteImplementationA());
client.ClientCode(abstraction);

Console.WriteLine();

abstraction = new ExtendedAbstraction(new ConcreteImplementationB());
client.ClientCode(abstraction);
}
}
}

执行结果:

1
2
3
4
5
Abstract: Base operation with:
ConcreteImplementationA: The result in platform A.

ExtendedAbstraction: Extended operation with:
ConcreteImplementationA: The result in platform B.

参考原文:桥接设计模式