一.意图
桥接是一种结构设计模式,允许你将一个大型类或一组密切相关的类拆分为两个独立的层级------抽象和实现------这些层级可以独立开发。
由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化。
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。------《设计模式》GoF
二.问题
抽象*?* *实施?*听起来很吓人吗?保持冷静,我们来举一个简单的例子。
假设你有一个几何类,包含一对子类:和。你想扩展这个职业层级以融入颜色,所以你计划创建和塑造子职业。不过,既然你已经有两个子职业,你需要创建四个职业组合,比如和。Shape``Circle``Square``Red``Blue``BlueCircle``RedSquare

在层级中添加新的形状类型和颜色,层级会呈指数增长。例如,要添加三角形形状,你需要引入两个子类,分别对应每种颜色。之后,添加新颜色则需要创建三个子类,分别对应每种形状类型。越往后,情况越糟。
三.解决方案
这个问题是因为我们试图在两个独立维度上扩展形状类:按形态和按颜色。这是类继承中非常常见的问题。
桥接模式试图通过从继承切换到对象组合来解决这个问题。这意味着你将一个维度提取到一个独立的类层级结构中,使原始类引用新层级中的对象,而不是将所有状态和行为集中在一个类中。

按照这种方法,我们可以将与颜色相关的代码提取成一个包含两个子类的独立类:和。然后该类会获得一个指向某个颜色对象的参考字段。现在形状可以将任何与颜色相关的工作委托给关联的颜色对象。这个参考将成为两职业之间的桥梁。从现在起,添加新颜色不需要改变形状层次,反之亦然。Red``Blue``Shape``Shape``Color
抽象与实现
《火焰的火焰》这本书引入了抽象和实现这两个术语,作为桥接定义的一部分。在我看来,这些术语听起来太学术化了,反而让模式看起来比实际复杂得多。读完带有形状和颜色的简单示例后,让我们来解读《火杯》书中那些恐怖词语背后的含义。
抽象*(也* 称为接口 )是某个实体的高级控制层。这个层本身不应该做什么真正的工作。它应该将工作委派给实现 层(也称为平台)。
注意,我们不是在谈论你编程语言中的接口 或抽象类。这两者不是同一回事。
在谈论实际应用时,抽象可以用图形用户界面(GUI)表示,实现则可能是GUI层根据用户交互调用的底层作系统代码(API)。
一般来说,你可以将这样的应用扩展到两个独立的方向:
-
拥有多个不同的图形界面(例如,针对普通客户或管理员定制的)。
-
支持多个不同的API(例如,能够在Windows、Linux和macOS下启动应用)。
在最坏的情况下,这个应用可能看起来像一个巨大的意大利面碗,数百个条件句将不同类型的图形界面和代码中各处的API连接起来。

你可以通过将与特定接口-平台组合相关的代码提取到不同的类中,来为这场混乱带来秩序。不过,很快你会发现这类*课程有很多。*类层级结构将呈指数增长,因为添加新的图形界面或支持不同的 API 需要创建越来越多的类。
让我们试着用桥接模式来解决这个问题。它建议我们将类别分为两个层级:
-
抽象:应用的图形界面层。
-
实现:作系统的API。

抽象对象控制应用的外观,将实际工作委托给关联的实现对象。只要遵循统一界面,不同实现可以互换,使同一图形界面在Windows和Linux下都能正常工作。
因此,你可以在不涉及API相关类的情况下更改GUI类。此外,添加对另一个作系统的支持只需在实现层级中创建一个子类。
四.桥模式结构

五.桥模式适合应用场景
-
当你想划分和组织一个拥有多个功能变体的单体类时,可以使用桥接模式(例如,该类是否能与多个数据库服务器兼容)。
课程越大,越难弄明白它是如何运作的,做出改变所需的时间也越长。对某一功能变体的更改可能需要在整个类别中进行调整,这常常导致错误或未能解决一些关键副作用。
桥梁模式允许你将单体职业拆分成多个职业层级。之后,你可以独立于其他层级更改每个层级中的类。这种方法简化了代码维护,并最大限度地降低了破坏现有代码的风险。
-
当你需要在多个正交(独立)维度上扩展一个类时,可以使用这个模式。
桥接建议你为每个维度提取一个独立的类层级结构。原始类将相关工作委托给属于这些层级的对象,而不是自己独自完成所有工作。
-
如果你需要在运行时切换实现,可以用桥接器。
虽然是可选的,但桥接模式允许你替换抽象中的实现对象。只需给字段赋予一个新值即可。
顺便说一句,这最后一点是很多人混淆桥牌和策略模式的主要原因。记住,模式不仅仅是某种课程结构。它也可能传达意图和正在解决的问题。
六.实现方式
-
识别你课堂中的正交维度。这些独立的概念可以是:抽象/平台、域/基础设施、前端/后端,或接口/实现。
-
查看客户端需要哪些作,并在基础抽象类中定义它们。
-
确定所有平台上可用的作。在通用实现界面中声明抽象所需的节点。
-
对于你领域内的所有平台,创建具体的实现类,但确保它们都遵循实现界面。
-
在抽象类中,添加实现类型的引用字段。抽象将大部分工作委托给该字段中引用的实现对象。
-
如果你有多个高级逻辑变体,可以通过扩展基础抽象类为每个变体创建精细抽象。
-
客户端代码应将实现对象传递给抽象的构造函数,使两者相互关联。之后,客户端可以不管实现,只处理抽象对象。
七.优缺点
-
优点:
-
你可以创建平台无关的类和应用。
-
客户端代码处理高级抽象。它没有暴露在平台细节上。
-
开闭原则。你可以独立引入新的抽象和实现。
-
单一责任原则。你可以在抽象中专注于高层逻辑,在实现中专注于平台细节。
-
-
缺点
- 你可以把模式应用到高度内聚的类上,让代码更复杂。
八.与其他模式的关系
-
桥接系统通常在前期设计,允许你独立开发应用的各个部分。另一方面,Adapter 通常与现有应用结合使用,使一些本不兼容的类能够良好协同工作。
-
桥梁、国家、战略(以及某种程度上的适配器)结构非常相似。事实上,所有这些模式都基于构图,即将工作委托给其他对象。不过,它们各自解决的问题不同。模式不仅仅是用来构建代码的具体配方。它还能向其他开发者传达该模式所解决的问题。
-
你可以用 Abstract Factory 和 Bridge。当Bridge定义的某些抽象只能与特定实现兼容时,这种配对非常有用。在这种情况下,Abstract Factory 可以封装这些关系,并对客户端代码隐藏复杂性。
-
你可以将Builder和Bridge结合使用:Director类扮演抽象角色,而不同的构建者则作为实现。
九.示例代码
/**
* 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.
*/
class Implementation {
public:
virtual ~Implementation() {}
virtual std::string OperationImplementation() const = 0;
};
/**
* Each Concrete Implementation corresponds to a specific platform and
* implements the Implementation interface using that platform's API.
*/
class ConcreteImplementationA : public Implementation {
public:
std::string OperationImplementation() const override {
return "ConcreteImplementationA: Here's the result on the platform A.\n";
}
};
class ConcreteImplementationB : public Implementation {
public:
std::string OperationImplementation() const override {
return "ConcreteImplementationB: Here's the result on the platform B.\n";
}
};
/**
* 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 {
/**
* @var Implementation
*/
protected:
Implementation* implementation_;
public:
Abstraction(Implementation* implementation) : implementation_(implementation) {
}
virtual ~Abstraction() {
}
virtual std::string Operation() const {
return "Abstraction: Base operation with:\n" +
this->implementation_->OperationImplementation();
}
};
/**
* You can extend the Abstraction without changing the Implementation classes.
*/
class ExtendedAbstraction : public Abstraction {
public:
ExtendedAbstraction(Implementation* implementation) : Abstraction(implementation) {
}
std::string Operation() const override {
return "ExtendedAbstraction: Extended operation with:\n" +
this->implementation_->OperationImplementation();
}
};
/**
* 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.
*/
void ClientCode(const Abstraction& abstraction) {
// ...
std::cout << abstraction.Operation();
// ...
}
/**
* The client code should be able to work with any pre-configured abstraction-
* implementation combination.
*/
int main() {
Implementation* implementation = new ConcreteImplementationA;
Abstraction* abstraction = new Abstraction(implementation);
ClientCode(*abstraction);
std::cout << std::endl;
delete implementation;
delete abstraction;
implementation = new ConcreteImplementationB;
abstraction = new ExtendedAbstraction(implementation);
ClientCode(*abstraction);
delete implementation;
delete abstraction;
return 0;
}
执行结果
Abstraction: Base operation with:
ConcreteImplementationA: Here's the result on the platform A.
ExtendedAbstraction: Extended operation with:
ConcreteImplementationB: Here's the result on the platform B.