C++设计模式 #6 桥模式(Bridge)

动机

  • 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个变化的维度。

如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度

举个栗子

我们有一个发送消息的抽象基类

class Messager{
public:virtual void Login(string name, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual void PlaySound() = 0;virtual void DrawShape() = 0;virtual void WriteText() = 0;virtual void Connect() = 0;virtual ~Messager() {}
};

在这种业务场景下,我们需要发送消息实现登录,发送文字,发送图片的功能。同时也可能有播放声音等等其他的功能需求。

针对不同的平台,我们需要不同的实现逻辑来完成基础需求。

class PCMessagerBase : public Messager{
public:virtual void PlaySound(){//PC平台的实现逻辑}virtual void DrawShape(){//PC平台的实现}virtual void WriteText(){//PC平台的实现}virtual void Connect(){//PC平台的实现}
};class MobileMessagerBase : public Messager{
public:virtual void PlaySound(){//Mobile平台的实现逻辑}virtual void DrawShape(){//Mobile平台的实现}virtual void WriteText(){//Mobile平台的实现}virtual void Connect(){//Mobile平台的实现}
};

针对具体的不同业务场景,我们需要有不同的逻辑。比如说,我们需要一种精简版的逻辑,同时需要一种完美版的逻辑,类似针对非会员和会员的不同处理😀

//业务逻辑
class PCMessagerLite : public PCMessagerBase{
public:virtual void Login(string name, string password){PCMessagerBase::Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message){PCMessagerBase::WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image){PCMessagerBase::DrawShape();    //发送图片前对图片的处理等等//...}
};class PCMessagerPerfect : public PCMessagerBase{
public:virtual void Login(string name, string password){PCMessagerBase::PlaySound();PCMessagerBase::Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message){PCMessagerBase::PlaySound();PCMessagerBase::WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image){PCMessagerBase::PlaySound();PCMessagerBase::DrawShape();    //发送图片前对图片的处理等等//...}
};

perfect版本在lite版本的基础上,可能有一些其他的行为,比如说播放一段音乐等等。

//业务逻辑
class MobileMessagerLite : public MobileMessagerBase{
public:virtual void Login(string name, string password){MobileMessagerBase::Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message){MobileMessagerBase::WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image){MobileMessagerBase::DrawShape();    //发送图片前对图片的处理等等//...}
};class MobileMessagerPerfect : public MobileMessagerBase{
public:virtual void Login(string name, string password){MobileMessagerBase::PlaySound();MobileMessagerBase::Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message){MobileMessagerBase::PlaySound();MobileMessagerBase::WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image){MobileMessagerBase::PlaySound();MobileMessagerBase::DrawShape();    //发送图片前对图片的处理等等//...}
};

在移动端平台上也是一样的。业务流程是一样的,可能有一些实现调用了MobileMessagerBase基类的方法。

存在的问题

现在存在的类的关系是这样的。这样的类中间存在的大量的重复代码,比如PCMessageLite和MobileMessageLite的Login函数中,逻辑明显是一样的,唯一的区别在于调用的基类的Connect函数不同。

这样明显是一种不好的设计。这与我们之前写过的装饰模式中的问题非常类似。C++设计模式 #5 装饰模式(Decorator)-CSDN博客

重构

如果参考装饰模式(Decorator)的方式,我们可以将代码重构成以下这种形式。

class MessagerLite{Messager* messager;		//在运行时 = new PCMessagerBase() 或者  MobileMessagerBase()
public:virtual void Login(string name, string password) {messager->Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message) {messager->WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image) {messager->DrawShape();    //发送图片前对图片的处理等等//...}
};class MessagerPerfect {Messager* messager;		//在运行时 = new PCMessagerBase() 或者  MobileMessagerBase()
public:virtual void Login(string name, string password) {messager->PlaySound();messager->Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message) {messager->PlaySound();messager->WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image) {messager->PlaySound();messager->DrawShape();    //发送图片前对图片的处理等等//...}
};

看起来这种方法是可行的,但是注意这种方法存在着致命的缺陷。

PCMessagerBase和MobileMessagerBase这两个类,只重载了Messager类中的两个三个方法,另外三个依然是纯虚函数。PCMessagerBase和MobileMessagerBase这两个类依然是纯虚基类,它们是不可以被实例化的,也就是说,我们在运行时是无法初始化MessagerLite的messager指针为PCMessagerBase的。

造成这种问题的原因是,Messager的这些函数放在一个类中,并不合适。我们将代码彻底重构成如下形式

class Messager {
protected:MessagerImp* messager;		//在运行时 = new PCMessagerBase() 或者  MobileMessagerBase()
public:virtual void Login(string name, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerImp {
public:virtual void PlaySound() = 0;virtual void DrawShape() = 0;virtual void WriteText() = 0;virtual void Connect() = 0;virtual ~MessagerImp() {}
};class PCMessagerBase : public MessagerImp {
public:virtual void PlaySound() {//PC平台的实现逻辑}virtual void DrawShape() {//PC平台的实现}virtual void WriteText() {//PC平台的实现}virtual void Connect() {//PC平台的实现}
};class MobileMessagerBase : public MessagerImp {
public:virtual void PlaySound() {//Mobile平台的实现逻辑}virtual void DrawShape() {//Mobile平台的实现}virtual void WriteText() {//Mobile平台的实现}virtual void Connect() {//Mobile平台的实现}
};//业务逻辑
class MessagerLite : public Messager{
public:virtual void Login(string name, string password) {messager->Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message) {messager->WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image) {messager->DrawShape();    //发送图片前对图片的处理等等//...}
};class MessagerPerfect : public Messager{
public:virtual void Login(string name, string password) {messager->PlaySound();messager->Connect();    //在登录前与服务器等等保持连接//...}virtual void SendMessage(string message) {messager->PlaySound();messager->WriteText();    //在发送消息前进行消息的输入//...}virtual void SendPicture(Image image) {messager->PlaySound();messager->DrawShape();    //发送图片前对图片的处理等等//...}
};

当前类的关系是这样的,我们成功的将业务上的扩展(MessagerLite/MessagerPerfect)与平台上的扩展(PCMessagerBase/MobileMessagerBase)两个方向上分开。用(n+m)数量的类,实现了(n*m)的功能。

模式定义

  • 将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。——《设计模式》GoF

同样红色的部分是稳定的,蓝色的部分是变化的。

体现在我们上面的代码中,就是Messager类与MessagerImp类中间搭了一座桥。使得业务功能与平台扩展两个方向上可以分开变化。

总结

  • 桥模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度变化。所谓抽象和实现沿着各自维度变化,即“子类化”
  • 桥模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性较差。桥模式是比多继承方案更好的解决办法。
  • 桥模式一般应用于“两个非常强的变化维度”,有时一个类有多于两个的维度变化,这时也可以使用桥模式的扩展模式。

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

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

相关文章

YUM和编译安装

一、安装Linux 1.编译安装,灵活性高,难度较大,可以安装较新的版本 2.rpm安装(redhat) linux包安装 rpm 软件名 3.yum yum是rpm升级版本,解决了rpm的弊端 2和3用的都是红帽打包好的软件包,能…

Elasticsearch基本使用

文章目录 概要一、核心概念二、索引操作2.1 创建索引2.2 判断索引是否存在2.3 查看索引2.4 打开、关闭索引2.5 删除索引 三、映射操作3.1 创建映射字段3.2 映射属性详解3.3 查看映射关系 四、文档增删改查4.1 新增文档4.2 查看单个文档4.3 查看所有文档4.4 _source定制返回字段…

小白也能搞定的Python选择排序

更多Python学习内容:ipengtao.com 大家好,我是彭涛,今天为大家分享 小白也能搞定的Python选择排序。全文3300字,阅读大约10分钟 选择排序(Selection Sort)是一种简单但有效的排序算法,它通过逐步…

IW5500手提式强光巡检工作灯

适用场所: 适用于铁路列检作业、工务巡道、车辆检修; 适用于冶金、厂电、网电等行业的巡查、设备检修等。 产品特点: 工作光、强光两种光设计,按动按钮可进行自由转换。 工作时间长,强光和工作光的连续工作时间分别在…

c++代码寻找USB00端口并添加打印机

USB00*端口的背景 插入USB端口的打印机,安装打印机驱动,在控制面板设备与打印机处的打印机对象上右击,可以看到打印机端口。对于不少型号,这个端口是USB001或USB002之类的。 经观察,这些USB00*端口并不是打印机驱动所…

制作自己的 Docker 容器

软件开发最大的麻烦事之一,就是环境配置。用户必须保证操作系统的设置,各种库和组件的安装,只有它们都正确,软件才能运行。docker从根本上解决问题,软件安装的时候,把原始环境一模一样地复制过来。 以 koa-…

为什么c++的开源库那么少?

为什么c的开源库那么少? 在开始前我有一些资料,是我根据自己从业十年经验,熬夜搞了几个通宵,精心整理了一份「 C的资料从专业入门到高级教程工具包」,点个关注,全部无偿共享给大家!!…

【JavaWeb学习笔记】15 - jQuery

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/jquery 目录 零、官方文档 一、jQuery基本介绍 1.基本介绍 2.原理图 二、JQuery入门使用 1.下载JQuery 2.jQuery快速入门 三、jQuery对象 1.什么是jQuery对象? 2.DOM对象转换成jQuery对象 …

C# 实现虚拟数字人

随着Ai技术的提升和应用,虚拟数字人被广泛应用到各行各业中。为我们的生活和工作提供了非常多的便利和色彩。 通过设置虚拟数字人的位置大小,可以让数字人可以在电脑屏幕各个位置显示: 虚拟数字人素材: 虚拟数字人(实际有语音&am…

Django之DRF框架三,序列化组件

一、序列化类的常用字段和字段参数 常用字段 字段名字段参数CharFieldmax_lengthNone, min_lengthNone, allow_blankFalse, trim_whitespaceTrueIntegerFieldmax_valueNone, min_valueNoneFloatFieldmax_valueNone, min_valueNoneBooleanFieldNullBooleanFieldFloatFieldmax_…

零基础学人工智能:TensorFlow 入门例子

识别手写图片 因为这个例子是 TensorFlow 官方的例子,不会说的太详细,会加入了一点个人的理解,因为TensorFlow提供了各种工具和库,帮助开发人员构建和训练基于神经网络的模型。TensorFlow 中最重要的概念是张量(Tenso…

基于Java SSM框架实现学生综合考评作业成绩管理系统项目【项目源码+论文说明】

基于java的SSM框架实现学生综合考评作业成绩管理系统演示 摘要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 学生综合考评管理系统,主要的模块包括查看;管理员&#xff1…