设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
桥接模式
> 桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类**拆分为抽象和实现两个独立的层次结构**, 从而能在开发时分别使用。 ![桥接设计模式](/media/202203/2022-03-05_2155110.41236352921088126.png) 桥接(Bridge)是用于**把抽象化与实现化解耦**,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。 这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。 ## 目的 将抽象与实现分离,这样两者可以独立地改变。 ## 问题 假如你有一个几何 形状 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. 猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。 2. 墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。 **优点:** 1. 抽象和实现的分离。 2. 优秀的扩展能力。 3. 实现细节对客户透明。 **缺点:** 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 **使用场景:** 1. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3. 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。 **注意事项:** 对于两个独立变化的维度,使用桥接模式再适合不过了。 ## 结构 ![桥接设计模式](/media/202203/2022-03-03_1159320.396895525582332.png) - 抽象部分 (Abstraction) 提供高层控制逻辑, 依赖于完成底层实际工作的实现对象。 - 实现部分 (Implementation) 为所有具体实现声明通用接口。 抽象部分仅能通过在这里声明的方法与实现对象交互。 抽象部分可以列出和实现部分一样的方法, 但是抽象部分通常声明一些复杂行为, 这些行为依赖于多种由实现部分声明的原语操作。 - 具体实现 (Concrete Implementations) 中包括特定于平台的代码。 - 精确抽象 (Refined Abstraction) 提供控制逻辑的变体。 与其父类一样, 它们通过通用实现接口与不同的实现进行交互。 - 通常情况下, 客户端 (Client) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。 - 我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。 ### 实例 我们有一个作为桥接实现的 DrawAPI 接口和实现了 DrawAPI 接口的实体类 RedCircle、GreenCircle。Shape 是一个抽象类,将使用 DrawAPI 的对象。BridgePatternDemo 类使用 Shape 类来画出不同颜色的圆。 ![桥接模式的 UML 图](https://www.runoob.com/wp-content/uploads/2014/08/20201015-bridge.svg) ## 伪代码 示例演示了桥接模式如何拆分程序中同时管理设备及其遥控器的庞杂代码。 设备 Device 类作为实现部分, 而 遥控器 Remote 类则作为抽象部分。 ![桥接模式示例的结构](/media/202203/2022-03-03_1359590.7682813555131173.png) 遥控器基类声明了一个指向设备对象的引用成员变量。所有遥控器通过通用设备接口与设备进行交互,使得同一个遥控器可以支持不同类型的设备。 你可以开发独立于设备类的遥控器类,只需新建一个遥控器子类即可。例如,基础遥控器可能只有两个按钮,但你可在其基础上扩展新功能,比如额外的一节电池或一块触摸屏。 客户端代码通过遥控器构造函数将特定种类的遥控器与设备对象连接起来。 ``` // “抽象部分”定义了两个类层次结构中“控制”部分的接口。它管理着一个指向“实 // 现部分”层次结构中对象的引用,并会将所有真实工作委派给该对象。 class RemoteControl is protected field device: Device constructor RemoteControl(device: Device) is this.device = device method togglePower() is if (device.isEnabled()) then device.disable() else device.enable() method volumeDown() is device.setVolume(device.getVolume() - 10) method volumeUp() is device.setVolume(device.getVolume() + 10) method channelDown() is device.setChannel(device.getChannel() - 1) method channelUp() is device.setChannel(device.getChannel() + 1) // 你可以独立于设备类的方式从抽象层中扩展类。 class AdvancedRemoteControl extends RemoteControl is method mute() is device.setVolume(0) // “实现部分”接口声明了在所有具体实现类中通用的方法。它不需要与抽象接口相 // 匹配。实际上,这两个接口可以完全不一样。通常实现接口只提供原语操作,而 // 抽象接口则会基于这些操作定义较高层次的操作。 interface Device is method isEnabled() method enable() method disable() method getVolume() method setVolume(percent) method getChannel() method setChannel(channel) // 所有设备都遵循相同的接口。 class Tv implements Device is // ... class Radio implements Device is // ... // 客户端代码中的某个位置。 tv = new Tv() remote = new RemoteControl(tv) remote.togglePower() radio = new Radio() remote = new AdvancedRemoteControl(radio) ``` ## 应用场景 - 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。 类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。 桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。 - 如果你希望在几个独立维度上扩展一个类, 可使用该模式。 桥接建议将**每个维度抽取为独立的类层次**。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。 - 如果你需要在运行时切换不同实现方法, 可使用桥接模式。 当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。 顺便提一句, 最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住, 设计模式并不仅是一种对类进行组织的方式, 它还能用于沟通意图和解决问题。 ## 实现方式 1. 明确类中**独立的维度**。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。 2. 了解客户端的业务需求, 并在抽象基类中定义它们。 3. 确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。 4. 为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。 5. 在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。 6. 如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。 7. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。 ## 优点 - 你可以创建与平台无关的类和程序。 - 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。 - 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。 - 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。 ## 缺点 - 对高内聚的类使用该模式可能会让代码更加复杂。 ## 与其他模式的关系 - 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。 - **桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。** 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 - 你可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。 - 你可以结合使用生成器模式和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。 ## 示例代码 ### TS ```ts /** * 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 implementation: Implementation; constructor(implementation: Implementation) { this.implementation = implementation; } public operation(): string { const result = this.implementation.operationImplementation(); return `Abstraction: Base operation with:\n${result}`; } } /** * You can extend the Abstraction without changing the Implementation classes. */ class ExtendedAbstraction extends Abstraction { public operation(): string { const result = this.implementation.operationImplementation(); return `ExtendedAbstraction: Extended operation with:\n${result}`; } } /** * 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. */ interface Implementation { operationImplementation(): string; } /** * Each Concrete Implementation corresponds to a specific platform and * implements the Implementation interface using that platform's API. */ class ConcreteImplementationA implements Implementation { public operationImplementation(): string { return 'ConcreteImplementationA: Here\'s the result on the platform A.'; } } class ConcreteImplementationB implements Implementation { public operationImplementation(): string { return 'ConcreteImplementationB: Here\'s the result on the platform B.'; } } /** * 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. */ function clientCode(abstraction: Abstraction) { // .. console.log(abstraction.operation()); // .. } /** * The client code should be able to work with any pre-configured abstraction- * implementation combination. */ let implementation = new ConcreteImplementationA(); let abstraction = new Abstraction(implementation); clientCode(abstraction); console.log(''); implementation = new ConcreteImplementationB(); abstraction = new ExtendedAbstraction(implementation); clientCode(abstraction); ``` 输出: ```txt 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. ```
追风者
2022年3月24日 13:16
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码