设计模式——桥接模式(结构型)

引言

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

问题

抽象? 实现? 听上去挺吓人? 让我们慢慢来, 先考虑一个简单的例子。

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

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

解决方案

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

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

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

抽象部分和实现部分

设计模式四人组的著作  在桥接定义中提出了抽象部分实现部分两个术语。 我觉得这些术语过于学术了, 反而让模式看上去比实际情况更加复杂。 在介绍过形状和颜色的简单例子后, 我们来看看四人组著作中让人望而生畏的词语的含义。

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

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

在实际的程序中, 抽象部分是图形用户界面 (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. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。

 桥接模式优缺点

  •  你可以创建与平台无关的类和程序。
  •  客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
  •  开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
  •  单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。
  •  对高内聚的类使用该模式可能会让代码更加复杂。

 与其他模式的关系

  • 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。

  • 桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 你可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

  • 你可以结合使用生成器模式和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/278101.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将 Github token 添加至远程仓库

将 Github token 添加至远程仓库后便于每次 push 重复输入的麻烦 首先,将已生成的 token 记录(注:生成后的 token 确认后便无法查看只能重新生成)并找到对应的项目 git 本地文件路径下 其次,将其与项目所关联,按如下格式配置即可 token 格式类似于 ghp_CAxxxxxxxxxxxxxxxxxGx5j…

C++STL中string详解(零基础/小白,字符串)

目录 1. 基本概念: 1.1 本质: 1.2 string和char*区别: 1.3 特点: 2. 构造函数(初始化) 3. 赋值操作 4. 字符串拼接 5 查找 和 替换 6. 字符串比较 7. 字符存取 8. 插入和删除 9. 子串获取 1. 基本概念: 1.…

字节与位在物联网传输协议中的使用

1个字节(byte) 8个位(bit) 如下例子,是一个上报数据类型的表格,总有48位(6个字节) 假如报文给的数据类型数据是: 0x06 时,06十六进制转为二进制,结果是00000110 那么在图下就是 (bit1 和 bit2 都为 1) &#xff…

[极客大挑战 2019]BuyFlag1

打开网站: 右上角有个菜单 (menu) ,先点一下,然后就进入了 pay.php 页面。 其中关键信息如下: ## FlagFlag need your 100000000 money### attentionIf you want to buy the FLAG:You must be a student from CUIT!!!You must…

【离线】牛客小白月赛39 G

登录—专业IT笔试面试备考平台_牛客网 题意 思路 考虑离线Bit做法 这种离线Bit,一般都是去考虑二维数点就能写清楚了 确定好两维:x 轴是1 ~ n, y 轴是 k 的大小 然后去遍历值域,如果值域很大的话需要排序离散化,但…

RT-DETR 图片目标计数 | 特定目标进行计数

全类别计数特定类别计数如何使用 RT-DETR 进行对象计数 有很多同学留言说想学 RT-DETR 目标计数。那么今天这篇博客,我将教大家如何使用 RT-DETR 进行对象计数。RT-DETR 是一种非常强大的对象检测模型,它可以识别图像中的各种对象。我们将学习如何利用这个模型对特定对象进行…

如何拍摄超级大像素图片,超级大像素有哪些应用

引言: 在数字摄影领域,超级大像素照片是指通过高像素的相机或拼接多张照片合成的照片。这样的照片具有更高的分辨率,细节更加清晰,绘画质感更强。那么如何拍摄超级大像素照片,超级大像素可以用在哪些领域呢。 一&…

希尔伯特黄变换(hht)

提示:主要对希尔伯特黄变换进行简略的介绍 一、希尔伯特黄变换是什么? 1.1 定义 为了纪念故事中两位老先生(Hilbert和Huang)的突出贡献,人们决定把“经过EMD分解出的IMF分量再经过Hilbert变换,最终得到信…

扫地机器人测试点

简介:在科技发展的浪潮下,扫地机器人已经成为越来越多家庭的“贴心管家”,助力我们迎战尘埃和杂物。但是,面对市面上五花八门的扫地机器人,以下为一般情况下扫地机器人的功能测试要点。 历史攻略: AI语音…

Vue3-19-组件-定义和基本使用

组件的定义 个人理解 :1、组件,就是我们把某个功能模块进行封装,在使用时直接引入进行使用,极大的提高了代码的可复用性。2、在vue 中,一个 [.vue] 文件,就是一个组件。3、组件之间存在【引入】 与 【被引…

Golang在 Docker 中交叉编译 Windows

前言: 前端时间把本地的 Golang 开发环境卸载了,如果编写代码的话就是启动一个 Golang 的 Docker 容器。这样做对于服务端开发本来也是没有问题的,但是有时候想要把程序放到 Windows 上面来执行,那就遇到麻烦了。因为 Docker 容器…

MSP430x44x Terminal Functions使用说明

DVcc1 1 I/O ADC中参考电压正极端子的输出 P6.3/A3 2 I/O 通用数字I/O/模拟输入a3--12位ADC P6.4/A4 3 I/O 通用数字I/O/模拟输入a4--12位ADC P6.5/A5 4 I/O 通用数字I/O/模拟输入a5--12位ADC P6.6/A6 5 I/O 通用数字I/O/模拟输入a6--12位ADC P6.7/A7/SVSI…