C++设计模式(11)——桥接模式

news2024/11/26 10:40:01

亦称: Bridge

意图

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

问题

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

假如你有一个几何 形状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) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。

伪代码

示例演示了桥接模式如何拆分程序中同时管理设备及其遥控器的庞杂代码。 ​ 设备Device类作为实现部分, 而 遥控器Remote类则作为抽象部分。
在这里插入图片描述
最初类层次结构被拆分为两个部分: 设备和遥控器。

遥控器基类声明了一个指向设备对象的引用成员变量。 所有遥控器通过通用设备接口与设备进行交互, 使得同一个遥控器可以支持不同类型的设备。

你可以开发独立于设备类的遥控器类, 只需新建一个遥控器子类即可。 例如, 基础遥控器可能只有两个按钮, 但你可在其基础上扩展新功能, 比如额外的一节电池或一块触摸屏。

客户端代码通过遥控器构造函数将特定种类的遥控器与设备对象连接起来。

// “抽象部分”定义了两个类层次结构中“控制”部分的接口。它管理着一个指向“实
// 现部分”层次结构中对象的引用,并会将所有真实工作委派给该对象。
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、如果你需要在运行时切换不同实现方法, 可使用桥接模式。

当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。

顺便提一句, 最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住, 设计模式并不仅是一种对类进行组织的方式, 它还能用于沟通意图和解决问题。

实现方式

1、明确类中独立的维度。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。

2、了解客户端的业务需求, 并在抽象基类中定义它们。

3、确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。

4、为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。

5、在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。

6、如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。

7、客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。

桥接模式优缺点

在这里插入图片描述

与其他模式的关系

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

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

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

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

C++ 桥接模式讲解和代码示例

桥接是一种结构型设计模式, 可将业务逻辑或一个大类拆分为不同的层次结构, 从而能独立地进行开发。

层次结构中的第一层 (通常称为抽象部分) 将包含对第二层 (实现部分) 对象的引用。 抽象部分将能将一些 (有时是绝大部分) 对自己的调用委派给实现部分的对象。 所有的实现部分都有一个通用接口, 因此它们能在抽象部分内部相互替换。

使用示例: 桥接模式在处理跨平台应用、 支持多种类型的数据库服务器或与多个特定种类 (例如云平台和社交网络等) 的 API 供应商协作时会特别有用。

识别方法: 桥接可以通过一些控制实体及其所依赖的多个不同平台之间的明确区别来进行识别。

概念示例

本例说明了桥接设计模式的结构并重点回答了下面的问题:

它由哪些类组成?
这些类扮演了哪些角色?
模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

/**
 * 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;
}

Output.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.

来源:https://refactoringguru.cn/design-patterns/bridge
仅供学习,非商业用途,侵删

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux笔记 diff及patch的制作与使用

相关命令展示 为方便查阅博客使用&#xff0c;预先展示相关命令 diff命令 diff -uN old.txt new.txt > patch_test.patch单个文件&#xff0c;不需要使用-r参数 diff 选项参数 旧文件&#xff08;夹&#xff09; 新文件&#xff08;夹&#xff09; > 补丁diff命令的常…

PS快速入门系列

01-界面构成 1菜单栏 2工具箱 3工县属性栏 4悬浮面板 5画布 ctr1N新建对话框&#xff08;针对画布进行设置&#xff09; 打开对话框&#xff1a;ctrl0&#xff08;字母&#xff09; 画布三种显示方式切换&#xff1a;F 隐藏工具箱&#xff0c;工具属性栏&#xff0c;悬浮面板…

Linux perf probe 的使用(三)

文章目录前言一、Dynamic Tracing二、kprobes2.1 perf kprobe 的使用2.2 kprobe Arguments3.3 tcp_sendmsg()3.3.1 Kernel: tcp_sendmsg()3.3.2 Kernel: tcp_sendmsg() with size3.3.2 Kernel: tcp_sendmsg() line number and local variable三、uprobes的使用3.1 perf uprobe …

PTA L1-043 阅览室

前言&#xff1a;内容包括四大模块&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读 题目&#xff1a; 天梯图书阅览室请你编写一个简单的图书借阅统计程序。当读者借书时&#xff0c;管理员输入书号并按下S键&#xff0c;程序开始计时&#xf…

Java NIO学习(一):Java NIO概述

一、 IO 概述IO 的操作方式通常分为几种&#xff1a;同步阻塞 BIO、同步非阻塞 NIO、异步非阻塞 AIO。&#xff08;1&#xff09;在 JDK1.4 之前&#xff0c;我们建立网络连接的时候采用的是 BIO 模式。&#xff08;2&#xff09;Java NIO&#xff08;New IO 或 Non Blocking I…

【C语言】速刷日记

这里总结10道C语言经典题型问题&#xff0c;有兴趣可以刷刷看。1.递归问题12.递归问题23.短路求值问题4.字符数组定义问题5.二维数组元素访问问题6.指针问题7.二维数组地址问题8.前置与后置问题9.求1的个数问题10.求1的个数移位操作符1.递归问题1 给定 fun 函数如下&#xff0…

Fluid-架构详细解析

前言Fluid 是云原生分布式数据集编排和加速引擎&#xff0c;主要服务于云原生场景下的数据密集型应用&#xff0c;例如大数据应用、AI 应用等。其目标是为AI与大数据云原生应用提供一层高效便捷的数据抽象&#xff0c;将数据从存储抽象出来&#xff0c;以便实现&#xff1a;通过…

服务调用分布式session

目录一、nginx动静分离二、服务调用1、创建配置zmall-cart购物车模块2、创建配置zmall-order订单模块3、服务调用三、spring session实战1、什么是Spring Session2、为什么要使用Spring Session3、错误案例展示4、配置spring-session四、二级域名问题五、用户登录一、nginx动静…

从某种程度上来看,产业互联网是一次对于互联网的弥补和修正

如果对当下我们正在经历的这样一个时代进行一次定义的话&#xff0c;我更加愿意将其划归到产业互联网的范畴里。可能有人会说&#xff0c;这与产业互联网并无联系&#xff0c;因为从本质上来看&#xff0c;当下我们所经历的这样一个时代&#xff0c;其实是与互联网并没有太多联…

(深度学习快速入门)第五章第一节2:GAN经典案例之MNIST手写数字生成

获取pdf&#xff1a;密码7281 文章目录一&#xff1a;数据集介绍二&#xff1a;GAN简介&#xff08;1&#xff09;简介&#xff08;2&#xff09;损失函数三&#xff1a;代码编写&#xff08;1&#xff09;参数及数据预处理&#xff08;2&#xff09;生成器与判别器模型&#x…

Acwing---112.雷达设备

雷达设备1.题目2.基本思想3.代码实现1.题目 假设海岸是一条无限长的直线&#xff0c;陆地位于海岸的一侧&#xff0c;海洋位于另外一侧。 每个小岛都位于海洋一侧的某个点上。 雷达装置均位于海岸线上&#xff0c;且雷达的监测范围为 d&#xff0c;当小岛与某雷达的距离不超…

2023-02-09 mysql/innodb存储

前言 大家都知道 MySQL 的数据都是保存在磁盘的,那具体是保存在哪个文件呢?MySQL 存储的行为是由存储引擎实现的,MySQL 支持多种存储引擎,不同的存储引擎保存的文件自然也不同。InnoDB 是我们常用的存储引擎,也是 MySQL 默认的存储引擎。本文主要以 InnoDB 存储引擎展开讨…

centos7.6 设置防火墙

1、查看系统版本 cat /etc/redhat-release2、查看防火墙运行状态 systemctl status firewalld这里可以看到当前是未运行状态(inactive)。 3、关闭开机自启动防火墙 systemctl disable firewalld.service4、启动防火墙并查看状态&#xff0c;系统默认 22 端口是开启的。 sy…

云原生微服务应用平台 EDAS 2022 年度报告

作者&#xff1a;孤戈 最近一年来&#xff0c;随着我们的客户对于云技术的诉求从资源快速交付的服务&#xff0c;转变为对资源精益运用的服务。EDAS 团队结合公共云上所服务的企业类客户的几万个应用&#xff0c;选取了 8 个最具代表性的指标&#xff0c;进行了一次系统性的分…

进度条实时显示request下载文件的解决方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,科大讯飞比赛第三名,CCF比赛第四名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Linux -文件打包和解压缩

1、概念 在 Windows 上最常见的不外乎这两种 .zip&#xff0c;.7z 后缀的压缩文件。而在 Linux 上面常见的格式除了以上两种外&#xff0c;还有 .rar&#xff0c;.gz&#xff0c;.xz&#xff0c;.bz2&#xff0c;.tar&#xff0c;.tar.gz&#xff0c;.tar.xz&#xff0c;*.tar…

python--turtle

前言 就随便练练&#xff0c;学习一下turtle库的使用 正文 1.语法学习 import turtle #导入库 turtle.showturtle() #画笔显示箭头 turtle.write("我是大帅逼") #写下字符串 turtle.forward(300) …

拦截器interceptor总结

拦截器一. 概念拦截器和AOP的区别&#xff1a;拦截器和过滤器的区别&#xff1a;二. 入门案例2.1 定义拦截器bean2.2 定义配置类2.3 执行流程2.4 简化配置类到SpringMvcConfig中一. 概念 引入&#xff1a; 消息从浏览器发送到后端&#xff0c;请求会先到达Tocmat服务器&#x…

C语言一维数组篇【下】——每日刷题经验分享

一维数组篇——每日刷题经验分享~&#x1f60e;前言&#x1f64c;有序序列插入一个整数 &#x1f60a;序列中删除指定数字 &#x1f60a;序列中整数去重小乐乐查找数字筛选法求素数总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦~ &#x1f60a;最喜欢的座右…

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(一)

ffplay入口 ffmpeg\fftools\ffplay.c int main(int argc, char **argv) {/*******************start 动态库加载/网络初始化等**************/int flags;VideoState *is;init_dynload();av_log_set_flags(AV_LOG_SKIP_REPEATED);parse_loglevel(argc, argv, options);/* regis…