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

news2024/11/26 17:35:52

动机

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

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

举个栗子

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

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.coloradmin.cn/o/1332354.html

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

相关文章

JavaOOP篇----第十五篇

系列文章目录 文章目录 系列文章目录前言一、有没有可能两个不相等的对象有相同的hashcode二、拷贝和浅拷贝的区别是什么?三、static都有哪些用法?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通…

基于SSM的双减后初小教育课外学习生活活动平台的设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

【零基础入门Docker】如何构建Web服务Dockerfile?

✍面向读者:所有人 ✍所属专栏:零基础入门Docker专栏https://blog.csdn.net/arthas777/category_12455882.html 目录 步骤1:第一步是构建我们的Docker文件,您可以使用vim编辑器。 步骤2:下一步是使用docker build命令…

学习stm32 模电数电需要学哪些?

学习stm32 模电数电需要学哪些? 在开始前我有一些资料,是我根据自己从业十年经验,熬夜搞了几个通宵,精心整理了一份「 stm32的资料从专业入门到高级教程工具包」,点个关注,全部无偿共享给大家!&…

帧内预测器的设计:提升视频编码效率的关键技术

随着互联网的迅猛发展,视频应用成为人们日常生活中不可或缺的一部分。然而,视频文件的传输和存储所需要的带宽和空间成本巨大。为了解决这个问题,视频编码技术应运而生。在视频编码中,帧内预测器是一项关键技术,通过利…

Linux-Keepalived(VRRP协议)高可用集群搭建

Linux-Keepalived(VRRP协议)高可用集群搭建 一、VRRP简介1.1 什么是VRRP?1.2 keepalived是什么?1.3 keepalived工作原理 二、实操配置过程2.1 试验模型2.2. Keepalived监控和维护VRRP集群的步骤2.2.1 安装keepalived2.2.2 配置kee…

力扣算法-Day1

160. 相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 示例 1: 输入:intersectVal 8, listA [4,1,8,4,5], listB [5,6,1,8,4,5], skipA 2, s…

嵌入式开发必须学习qt吗?

嵌入式开发必须学习qt吗? 在开始前我有一些资料,是我根据自己从业十年经验,熬夜搞了几个通宵,精心整理了一份「 嵌入式的资料从专业入门到高级教程工具包」,点个关注,全部无偿共享给大家!&#…

STM32位带

GPIO_SetBits(GPIOF,GPIO_Pin_9);修改为PFout(9)1; GPIO_ResetBits(GPIOF,GPIO_Pin_9);修改为PFout(9)0; 位带的定义: 支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM3 中,有两个区中实现了位带。其中一个是S…

Flink系列之:Checkpoints 与 Savepoints

Flink系列之:Checkpoints 与 Savepoints 一、概述二、功能和限制 一、概述 从概念上讲,Flink 的 savepoints 与 checkpoints 的不同之处类似于传统数据库系统中的备份与恢复日志之间的差异。 Checkpoints 的主要目的是为意外失败的作业提供恢复机制。 …

python实现元旦多种炫酷高级倒计时_附源码【第19篇—python过元旦】

文章目录 🌍python实现元旦倒计时 — 初级(控制台)⛅实现效果🌋实现源码🌜源码讲解 🌍python实现元旦倒计时 — 中级(精美动态图)⛅实现效果🌋实现源码🌜源码讲解 🌍python实现元旦倒计时 — 高…

中北大学 软件构造 U+

作业1 1.数据类型可分为两类:(原子类型) 、结构类型。 2.(数据结构)是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合 3.代码重构指的是改变程序的(结构)而不改变其行为,以便提高代码的可读性、易修改性等。 4.软件实…

【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成

《博主简介》 小伙伴们好,我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推荐--…

设计模式----解释器模式

一、简介 解释器模式使用频率并不高,通常用来构建一个简单语言的语法解释器,它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、sql解析等。 解释器模式是行为型设计模式之一,它的原始定义为:用于定义…

GIT具体配置步骤详解

GIT配置具体步骤如下 SDK 使用 Repo 工具管理,拉取 SDK 需要配置安装 Repo 工具。 Repo is a tool built on top of Git. Repo helps manage many Git repositories, does the uploads to revision control systems, and automates parts of the development workf…

4.svn版本管理工具使用

1. 什么是SVN 版本控制 它可以记录每一次文件和目录的修改情况,这样就可以借此将数据恢复到以前的版本,并可以查看数据的更改细节! Subversion(简称SVN)是一个自由开源的版本控制系统。在Subversion管理下,文件和目录可以超越时空 SVN的优势 统一的版本号 Subversi…

MySQL子查询、WITH AS、LAG查询统计数据实战

需求 给出一个比较常见的统计类业务需求:统计App(包括iOS和Android两大类)每日新注册用户数、以及累计注册用户数。 数据库采用MySQL,根据上面的需求,不难设计表如下: create table os_day_count(stat_d…

【必读】从MII到RGMII,一文了解以太网PHY芯片不同传输接口信号时序!

1、概述 不管是使用FPGA还是ARM,想要实现以太网通信,都离不开以太网PHY芯片,其功能如下所示,FPGA或者ARM将以太网数据发送给PHY芯片,PHY会将接收数据转换成模拟的差分信号传输到RJ45座子,最后通过网线与CPU…

数据库之MySQL的介绍

操作系统: windows:win10、win11、win7、windows Server2016 Linux/Unix :红帽(RedHat)、Bebian、SUSE MacOS Linux系统:CantOS(yum、dnf)、Ubuntu(apt、apt—get&am…

IP应用场景的规划

IP地址作为互联网通信的基石,在现代社会中扮演着至关重要的角色。本文将深入探讨IP地址在不同应用场景中的规划与拓展,探讨其在网络通信、安全、商业、医疗和智能城市等领域的关键作用与未来发展趋势。 IP地址的基本原理 IP地址是分配给网络上设备的数…