C++设计模式:桥模式(五)

1、定义与动机
  • 桥模式定义:将抽象部分(业务功能)与实现部分(平台实现)分离,使他们可以独立地变化
  • 引入动机
    • 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化
    • 如何应对这种“多维度的变化”?如何利用面向对象技术来使用类型可以轻松地沿着两个乃至多个方向变化,二部引入额外的复杂度?
2、案例分析

假设现在存在这样一个需求:

  • 有一个消息发送器Messager,里面有七个方法
    • 大致需要完成登录、发送文本消息、发送图片消息、播放声音、建立连接等
    • 同时由于存在不同的终端设备:例如手机、电脑、pad等,他们的这些实现方式肯定会存在一定的不同
    • 并且对于同一种设备存在不同的功能组合问题:例如发送图片、消息之前播放声音等。
2.1、首先设计一个抽象接口

根据上面所提出的需求可以写出如下的接口代码,函数都是纯虚函数和虚函数组成

class Messager{
public:virtual void Login() = 0;virtual void SendMessage() = 0;virtual void SendPicture() = 0;virtual void PlaySound() = 0;virtual void DrawShape() = 0;virtual void WriteText() = 0;virtual void Connect() = 0;virtual ~Messager(){}
};
2.2、针对不同的终端平台编码设计
  • 例如针对PC、手机…平台设计它们的是如何发送文本、画图、播放声音、建立网络通信连接的代码
  • 但是这些Base类依然是一个抽象基类,因为存在一些纯虚函数没有实现,因此其本身也是一个抽象类
// PC平台实现(抽象类),没有完全实现完毕所有的纯虚函数
class PCMessagerBase: public Messager{
public:virtual void PlaySound(){// *****}virtual void DrawShape(){// *****}virtual void WriteText(){// *****}virtual void Connect(){// *****}
}// Mobile平台实现(抽象类),没有完全实现完毕所有的纯虚函数
class MobileMessagerBase: public Messager{
public:virtual void PlaySound(){// *****}virtual void DrawShape(){// *****}virtual void WriteText(){// *****}virtual void Connect(){// *****}
}
2.3、针对同平台不同量级的实现
  • PCMessagerLite:轻量级实现,该实现类只完成最基本的功能,普通登录、发送图片文本消息
  • PCMessagerPerfect:完美级实现,在执行这些操作之前可以辅助带一些播放声音等额外的功能
class PCMessagerLite: public PCMessagerBase{
public:virtual void Login(){PCMessagerBase::Connect();// ******}virtual void SendMessage(){PCMessagerBase::WriteText();// ******}virtual void SendPicture(){PCMessagerBase::DrawShape();// ******}
};class PCMessagerPerfect: public PCMessagerBase{
public:virtual void Login(){PCMessagerBase::PlaySound();PCMessagerBase::Connect();// ******}virtual void SendMessage(){PCMessagerBase::PlaySound();PCMessagerBase::WriteText();// ******}virtual void SendPicture(){PCMessagerBase::PlaySound();PCMessagerBase::DrawShape();// ******}
};/* MobileMessagerLite、MobileMessagerPerfect的实现如上略过
*/
2.4、分析
  • 很明显针对上面的代码一眼就能看出:存在大量的代码重复PCMessagerLite和MobileMessagerLite的代码区别完全就在于类名不同、继承的Base类不同

    • 这一问题很好解决:首先把继承变成组合,把继承的Base类组合到Lite和Perfect类中

    • 此时只是子类不同:由于子类都来自同一个基类Messager,那么可以使用多态,进行成员声明类型的改变。

    • 最后在使用时动态的传入所需要的不同平台的实现即可。

  • 这样实现会导致一个类的急剧膨胀,假设只有1个Messager的接口、n个Base类、那么不同量级的实现是n * m的,总体是一个 1 + n + n * m + …

  • 这一点和装饰器模式很像,不过装饰器模式是一个阶乘的爆炸,但这里的工作量也不低

3、桥模式

针对上面所发现的弊端进行改进,首先可以进行的改进

3.1、改进(一)
  • 将继承改成组合的方式,可以很容易的写出PCMessagerLite、MobileMessagerLite的实现代码
class PCMessagerLite{
private:PCMessagerBase* pcMessagerBase;
public:virtual void Login(){pcMessagerBase->Connect();// ******}virtual void SendMessage(){pcMessagerBase->WriteText();// ******}virtual void SendPicture(){pcMessagerBase->DrawShape();// ******}
};class MobileMessagerLite{
private:MobileMessagerBase* mobileMessagerBase;
public:virtual void Login(){mobileMessagerBase->Connect();// ******}virtual void SendMessage(){mobileMessagerBase->WriteText();// ******}virtual void SendPicture(){mobileMessagerBase->DrawShape();// ******}
};
3.2、改进(二)
  • 然后其实可以将PCMessagerLite、MobileMessagerLite提炼成一个代码,这两份代码核心不点在于各自做组合的对象不同
  • 然而恰巧不巧的事:它们所组合的对象都来自同一个基类,因此可以提炼成MessagerLite代码
class MessagerLite{
private:Messager* messager;
public:MessagerLite(Messager *msg): messager(msg){}virtual void Login(){messager->Connect();// ******}virtual void SendMessage(){messager->WriteText();// ******}virtual void SendPicture(){messager->DrawShape();// ******}
};
  • 但是写完这个代码会有一个问题:基类PCMessagerBase和基类MobileMessagerBase是抽象类(没有完全实现所有的纯虚函数),而抽象类是无法实例化对象的,因此这个代码是有问题的,无法过编译。

  • 仔细分析问题核心所在点:

    • 一路下来,我们只在最后不同量级的代码编写实现Messager抽象类的login、SendMessage、SendPicture三个纯虚函数
    • 之前的那些Base基类并没有实现这三个方法才导致它们依然是一个抽象类
    • 所以问题的核心点:Messager抽象类的接口方法太多,官方一点的术语:职责不够单一(违背单一职责原则)
3.3、完美改进(三)
  • 为了解决单一职责问题,可以将Messager抽象接口的拆成两个抽象接口
  • Base类继承实现一个接口
  • 不同量级实现一个接口,然后组合Base类的指针(这里动态传入Base类不同的指针)
class Messager{
public:virtual void Login() = 0;virtual void SendMessage() = 0;virtual void SendPicture() = 0;virtual ~Messager(){}
};class MessagerImpl{virtual void PlaySound() = 0;virtual void DrawShape() = 0;virtual void WriteText() = 0;virtual void Connect() = 0;
};// PC平台实现
class PCMessagerBase: public MessagerImpl{
public:virtual void PlaySound(){// *****}virtual void DrawShape(){// *****}virtual void WriteText(){// *****}virtual void Connect(){// *****}
}// Mobile平台实现
class MobileMessagerBase: public MessagerImpl{
public:virtual void PlaySound(){// *****}virtual void DrawShape(){// *****}virtual void WriteText(){// *****}virtual void Connect(){// *****}
}class MessagerLite: public Messager{        // 实现抽象类
private:MessagerImpl* messagerImpl;             // 组合对象
public:MessagerLite(MessagerImpl *msgi): messagerImpl(msgi){}virtual void Login(){messager->Connect();// ******}virtual void SendMessage(){messager->WriteText();// ******}virtual void SendPicture(){messager->DrawShape();// ******}
};class MessagerPerfect: public Messager{     // 实现抽象类
private:MessagerImpl* messagerImpl;             // 组合对象
public:MessagerPerfect(MessagerImpl *msgi): messagerImpl(msgi){}virtual void Login(){MobileMessagerBase::PlaySound();MobileMessagerBase::Connect();// ******}virtual void SendMessage(){MobileMessagerBase::PlaySound();MobileMessagerBase::WriteText();// ******}virtual void SendPicture(){MobileMessagerBase::PlaySound();MobileMessagerBase::DrawShape();// ******}
};
  • 这样就能完成所有的功能:其核心点在于使用继承 + 组合的模式取代单一的继承方式来实现所有的功能
  • 采用桥模式的设计方式将类的膨胀改写成:1 + n + m的形式,整整比普通模式少了至少一个数量级的代码
4、总结
  • Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,即“子类化”它们。
  • Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类存在多个变化的原因和方向),复用性比较差。Bridge模式是比多级车工方案更好的解决方案。
  • Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也可以有多余两个的变化维度,这时可以使用Bridge的扩展模式。
    在这里插入图片描述

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

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

相关文章

QT 使用redis ,连接并使用

一.redis安装 链接:https://pan.baidu.com/s/17fXKOj5M4VIypR0y5_xtHw 提取码:1234 1.下载得到文件夹如图 course_redis为安装包。 2.启动Redis服务 把安装包解压到某个路径下即可。 打开cmd窗口,切换到Redis安装路径,输入 r…

4月21日,Sui成都开发者茶话会诚邀您来

由 Sui Foundation, 开发者教育平台 HackQuest,MoveBit,PoP Planet 共同主办的 Sui 成都开发者茶话会将于 4 月 21 日下午 2:00–6:00 举办,我们诚挚邀请所有对 Sui 生态,Sui Move 语言,和 Web3 开发有兴趣的小伙伴前来…

基于Springboot框架北京某大学失物招领系统设计与实现 研究背景和意义、国内外现状_失物招领发展现状

此外,基于Springboot框架的北京某大学失物招领系统还具有重要的社会意义。该系统可以为广大师生提供更加便捷、高效的失物招领服务,减少因失物而带来的经济损失和精神困扰。同时,该系统还可以促进校园文化的建设和传播,营造和谐、…

【UE 网络】DS框架学习路线

目录 0 引言1 如何学习DS框架1. 熟悉Unreal Engine基础2. 学习网络编程基础3. 掌握UE网络概念4. 实践和实验5. 加入社区和论坛6. 官方示例和案例研究7. 专业书籍和在线课程 2 DS框架重要知识点有哪些1. 网络复制2. 远程过程调用(RPC)3. 客户端服务器架构…

用函数指针写两个操作数的相关运算

文章目录 概要整体架构流程代码实现小结 概要 我们以加,减,乘,除为例来示范 整体架构流程 首先我们先实现一个菜单功能来进行选择:把他封装成一个menu函数 然后把加减乘除分别用不同的函数实现 为了选择我们选择使用switch来…

python实现OCR:pytesseract和pyddleocr(附代码)

文章目录 背景pytesseractpaddleocr百度apipaddleocr 背景 OCR是光学字符识别(Optical Character Recognition)的缩写,通过扫描等光学输入方式和文字识别将图片中的文字提取出来,非常适用于提取网络截图或扫描pdf等文件里的文本。…

JSON字符串中获取一个特定字段的值

JSON字符串中获取一个特定字段的值 一、方式一,引用gson工具二、方式二,使用jackson三、方式三,使用jackson转换Object四、方式四,使用hutool,获取报文数组数据 一、方式一,引用gson工具 测试报文&#xf…

每日五道java面试题之ZooKeeper篇(一)

目录: 第一题. ZooKeeper 是什么?第二题. Zookeeper 文件系统第三题. Zookeeper 怎么保证主从节点的状态同步?第四题. 四种类型的数据节点 Znode第五题 . Zookeeper Watcher 机制 – 数据变更通知 第一题. ZooKeeper 是什么? Zoo…

每日一题 第八十二期 CodeTON Round 8 (Div. 1 + Div. 2, Rated, Prizes!)

C1. Bessie’s Birthday Cake (Easy Version) time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input output: standard output Proof Geometric Construction Can Solve All Love Affairs - manbo-p ⠀ This is the easy versio…

【C++ STL算法】sort 排序

文章目录 【 1. 基本原理 】【 2. sort 的应用 】实例 - sort 函数实现 升序排序和降序排序 函数名用法sort (first, last)基于 快速排序,对容器或普通数组中 [ first, last ) 范围内的元素进行排序,默认进行升序排序(从小到大)。…

幸运数(蓝桥杯)

该 import java.util.*; public class Main {public static void main(String[] args) {Scanner scannew Scanner(System.in);int cnt0;for(int i1;i<100000000;i) {String si"";int lens.length();if(len%2!0) continue;int sum10; //左边int sum20; //右边fo…

CLIP模型入门

简介 CLIP&#xff08;Contrastive Language-Image Pre-Training&#xff09;是OpenAI在2021年初发布的多模态预训练神经网络模型&#xff0c;用于匹配图像和文本。该模型的关键创新之一是将图像和文本映射到统一的向量空间&#xff0c;通过对比学习的方式进行预训练&#xff…