C++设计模式结构型模式———桥接模式

文章目录

  • 一、引言
  • 二、桥接模式
  • 三、总结

一、引言

桥接(Bridge)模式也叫桥梁模式,简称桥模式,是一种结构型模式。该模式所解决的问题非常简单,即根据单一职责原则,在一个类中,不要做太多事,如果事情很多,尽量拆分到多个类中去,然后在一个类中包含指向另外一个类对象的指针,当需要执行另外一个类中的动作时,用指针直接去调用另外一个类的成员函数。


二、桥接模式

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

我们举个例子来说明: 继续前面的闯关打斗类游戏。在游戏中,不可避免地要显示各种图像,例如,人物头像、血条、人物背包、各种物品道具等,这些图像源自各种图像文件,从图像文件中把数据读出来并按照一个事先约定好的格式规范保存到一个缓冲区中以方便后续统一的显示处理。

现在的问题是图像文件有多种格式,常用的包括png、jpg、bmp等,为了把数据从这些不同格式的图像文件中读出(注意,不管文件是什么格式,读出到事先约定好的缓冲区中后都变成遵循相同规范的数据,此时这些数据不再有来自不同文件的区别)并显示,程序创建了一个叫作Image的父类以及分别叫作Image_pngImage_jpgImage_bmp的子类,代码如下:

class Image {
public:void draw(const string& pfilename) {int iLen = 0;string pData = parsefile(pfilename, iLen);if (iLen > 0) {cout << "显示pData所指向的缓冲区中的图像数据." << endl;}}virtual ~Image() {}private://根据文件名分析文件内容,每个子类因为图像文件格式不同,会有不同的读取和处理代码virtual string parsefile(const string& pfilename, int& iLen) = 0;
};// 处理png格式的图像文件
class Image_png : public Image {
private:virtual string parsefile(const string& pfilename, int& iLen) override {cout << "开始分析png文件中的数据并将分析结果放到pData中,";iLen = 100; // 模拟长度string data(iLen, 'x'); // 使用字符串初始化模拟数据return data;}
};// 处理jpg格式的图像文件
class Image_jpg : public Image {
private:virtual string parsefile(const string& pfilename, int& iLen) override {cout << "开始分析jpg文件中的数据并将分析结果放到pData中,";iLen = 150; // 模拟长度string data(iLen, 'y'); // 使用字符串初始化模拟数据return data;}
};// 处理bmp格式的图像文件
class Image_bmp : public Image {
private:virtual string parsefile(const string& pfilename, int& iLen) override {cout << "开始分析bmp文件中的数据并将分析结果放到pData中,";iLen = 200; // 模拟长度string data(iLen, 'z'); // 使用字符串初始化模拟数据return data;}
};

为了扩大游戏的受众并增加营收,这款游戏需要支持多个操作系统,包括 Windows、Linux 和 macOS。然而,这带来了一个问题:每个操作系统在显示图像数据时的实现代码都不同。虽然 parsefile 成员函数的实现可以与操作系统无关,但 draw 成员函数中的显示代码则需要针对不同操作系统进行调整。

因此,程序员不得不为现有的 Image 类的子类(如 Image_pngImage_jpgImage_bmp)创建额外的子类,以适配每个操作系统。这就意味着,如果我们为每种图像格式都增加三种操作系统的适配,原本的结构会变得非常复杂。

例如,原本有 3 个子类,现在需要为每个图片类型子类再增加 3 个操作系统的子类,总共会增加到 9 个新类,加上原有的 4 个类,总共是 13 个类。如果再支持一种新的图像格式,比如 GIF,就需要增加到 17 个类。而如果再增加一个新的操作系统,比如 Android,那么类的数量会增加到 21 个。

很明显,采用继承结构来设计类在这种情况下不是一个好方法。每当我们需要支持新的图像格式或新的操作系统时,类的数量就会迅速增加,导致代码变得复杂且难以维护。

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

因此,我们不难发现,其实没有必要把图像文件格式和操作系统类型掺和到一起通过继承设计出一系列类(例如Image_jpg_Linux这种类),这样设计是违反单一职责原则的,可以像下面这样做:

  • 把图像文件格式单独设计成一个继承关系的类,在其中实现parsefile成员函数(因为该成员函数只与图像文件格式有关)。
  • 操作系统类型也单独设计成一个继承关系的类,在其中实现draw成员函数(因为该成员函数只与操作系统类型有关)。

这样无论是扩充图像文件格式还是操作系统类型这两组类中的哪一组,都不会影响另外一组类,也就不会造成子类数量的急速增长。当然,在图像文件格式表示的类中有一个指向操作系统类型表示的类对象的指针,从而构成这两个类之间的委托关系。下面给出改造后的代码:

// 操作系统相关的接口
class ImageOS {
public:virtual void draw(const string& data, int iLen) = 0;virtual ~ImageOS() {}
};// Windows 显示实现
class ImageOS_Windows : public ImageOS {
public:void draw(const string& data, int iLen) override {cout << "在 Windows 上显示图像数据: " << data << endl;}
};// Linux 显示实现
class ImageOS_Linux : public ImageOS {
public:void draw(const string& data, int iLen) override {cout << "在 Linux 上显示图像数据: " << data << endl;}
};// macOS 显示实现
class ImageOS_Mac : public ImageOS {
public:void draw(const string& data, int iLen) override {cout << "在 macOS 上显示图像数据: " << data << endl;}
};

紧接着,再给一个ImageFormat类,以及图像文件格式相关类。

// 图像格式基类
class ImageFormat {
public:ImageFormat(unique_ptr<ImageOS> pimgos) : m_pImgOS(move(pimgos)) {}virtual void parsefile(const string& pfilename) = 0;virtual ~ImageFormat() {}protected:unique_ptr<ImageOS> m_pImgOS; // 委托
};// 处理png格式的图像文件
class Image_png : public ImageFormat {
public:Image_png(unique_ptr<ImageOS> pimgos) : ImageFormat(move(pimgos)) {}void parsefile(const string& pfilename) override {cout << "开始分析 PNG 文件: " << pfilename << endl;//...}
};
// 处理png格式的图像文件
class Image_jpg : public ImageFormat {
public:Image_jpg(unique_ptr<ImageOS> pimgos) : ImageFormat(move(pimgos)) {}void parsefile(const string& pfilename) override {cout << "开始分析 JPG 文件: " << pfilename << endl;//...}
};
// 处理bmp格式的图像文件
class Image_bmp : public ImageFormat {
public:Image_bmp(unique_ptr<ImageOS> pimgos) : ImageFormat(move(pimgos)) {}void parsefile(const string& pfilename) override {cout << "开始分析 BMP 文件: " << pfilename << endl;//...}
};

我们使用时:

unique_ptr<ImageOS> windowsOS = make_unique<ImageOS_Windows>();
unique_ptr<ImageFormat> pngImage = make_unique<Image_png>(move(windowsOS));pngImage->parsefile("image.png"); // 解析并显示图像数据unique_ptr<ImageOS> linuxOS = make_unique<ImageOS_Mac>();
unique_ptr<ImageFormat> pngImageLinux = make_unique<Image_png>(move(linuxOS));pngImageLinux->parsefile("image.png"); // 解析并显示图像数据

上述内容并不难理解。

在这里插入图片描述

此时,如果增加一个对.gif文件格式的支持,需要增加一个Image_gif子类(以ImageFormat作父类),而不需要改动ImageOS和其于类,这主要得益于ImageFormat子类中的parsefile成员函数得到的是一个事先约定好格规范的缓冲区数据,这些缓冲区数据已经脱离了原始的图像文件格式(png、jpg、bmp等)采用了一种统一的格式来表达,所以在执行ImageFormat子类的parsefile成员函数时,所遇到的m_pImgOS->draw(presult,iLen)代码行会直接调用ImageOS子类的draw方员函数,在这个draw成员函数中,并不需要区分原始的图像数据来自何种格式的图像格式。

桥接模式结构

在这里插入图片描述

引入桥接模式的定义:将抽象部分与实现部分分离,使它们都可以独立弟变化和扩展。

  • 抽象部分一般指业务功能,例如ImageFormat类,用于解析各种不同的图像文件格式,这就归为业务功能。
  • 实现部分一般指具体的平台实现,例如ImageOS类,用于根据不同的操作系统来绘制图像,这就归为平台实现。

也就是说,抽象部分是图像文件格式,而实现部分是OS的类型。桥接模式定义的意思就是把这两个维度分开,每个维度可以独立的变化。


三、总结

在这里插入图片描述

ImageFormat类和ImageOS之间的关系就叫桥接。用“桥接”设计模式定义中用到的术语来说,桥接就在抽象部分与实现部分之间担当着桥梁作用,桥梁两侧的每一部分又都可以独立变化。

在桥接模式的UML图中,存在四种角色:

  1. 抽象部分接口Abstraction):这个角色定义了一个抽象类的接口,并包含一个指向Implementor类型对象的指针。ImageFormat类扮演了这个角色。
  2. 扩展抽象部分接口RefinedAbstraction):这个角色实现了在Abstraction中定义的接口,并且可以调用Implementor中定义的方法。Image_pngImage_jpgImage_bmp`这些类扮演了这个角色。
  3. 实现部分接口Implementor):这个角色定义了实现类的接口,这些接口可能与Abstraction中的接口相似,也可能完全不同。通常Implementor提供的接口只包含基本操作,而Abstraction中的接口则实现更复杂的功能。ImageOS类扮演了这个角色。
  4. 具体实现类ConcreteImplementor):这个角色实现了Implementor中定义的接口。 ImageOS_WindowsImageOS_LinuxImageOS_Mac这些类扮演了这个角色。

桥接模式用组合关系解决了传统继承关系存在的类数量爆炸式增长的问题,使用对象组合方式解决问题,使代码更灵活、更易于扩展。桥接模式的实现代码不仅体现了单一职责原则,还体现了开闭原则、组合复用原则、依赖倒置原则等。

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

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

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

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

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

相关文章

【android12】【AHandler】【4.AHandler原理篇ALooper类方法全解】

AHandler系列 【android12】【AHandler】【1.AHandler异步无回复消息原理篇】-CSDN博客 【android12】【AHandler】【2.AHandler异步回复消息原理篇】-CSDN博客 【android12】【AHandler】【3.AHandler原理篇AHandler类方法全解】-CSDN博客 其他系列 本人系列文章-CSDN博客…

基于SSM志愿者招募系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;志愿组织管理&#xff0c;组织信息管理&#xff0c;组织申请管理&#xff0c;志愿活动管理活动报名管理 用户账号功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;…

selinux介绍和Linux中的防火墙

selinux 1、selinux的说明 2、selinux的工作原理 3、selinux的启动、关闭与查看 防火墙 1、什么是防火墙 2、iptables &#xff08;1&#xff09;iptables介绍 参数说明 3、firewalld firewalld-cmd的参数说明

RHCE4

一、web服务器简介 1、什么是www www 是 world wide web 的缩写&#xff0c;也就是全球信息广播的意思。通常说的上网就是使用 www 来查询用户所需要的信息。 www 可以结合文字、图形、影像以及声音等多媒体&#xff0c;并通过可以让鼠标单击超链接的方式将信息以 Internet 传…

推荐一款用来快速开发3D建筑模型软件:Allplan

Nemetschek Allplan是一款用来快速开发3D建筑模型软件的软件&#xff0c;它的简单好用但是功能强大&#xff0c;绝对不输AuToDesk&#xff0c;而且人性化的设计更让你可以快速的建立开发及维护你的建筑模型。是与ArchiCAD称兄道弟的强大建筑设计软件&#xff0c;功能上互有短长…

Python面向对象,实现图片处理案例,支持:高斯模糊、Canny边缘检测、反转边缘图像、生成手绘效果、调亮度......等等

实验图片如下&#xff1a; 命名为img1.jpg, 放在项目下新建文件夹images下 项目构造如下&#xff1a; app.py源码如下 import cv2 import os from matplotlib import pyplot as plt import numpy as npclass ImageProcessor:def __init__(self, image_path):self.image cv…

利用LangChain与LLM打造个性化私有文档搜索系统

我们知道LLM&#xff08;大语言模型&#xff09;的底模是基于已经过期的公开数据训练出来的&#xff0c;对于新的知识或者私有化的数据LLM一般无法作答&#xff0c;此时LLM会出现“幻觉”。针对“幻觉”问题&#xff0c;一般的解决方案是采用RAG做检索增强。 但是我们不可能把…

C++ 基础语法 一

C 基础语法 一 文章目录 C 基础语法 一const 限定符常量指针类型别名autodecltypeQStringvector迭代器指针和数组显示转换static_castconst_cast 函数尽量使用常量引用数组形参不要返回局部对象的引用和指针返回数组指针 C四种转换内联函数constexpr函数函数指针 const 限定符 …

tensorflow案例4--人脸识别(损失函数选取,调用VGG16模型以及改进写法)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 这个模型结构算上之前的pytorch版本的&#xff0c;算是花了不少时间&#xff0c;但是效果一直没有达到理想情况&#xff0c;主要是验证集和训练集准确率…

力扣每日一题 超级饮料的最大强化能量 动态规划(dp)

来自未来的体育科学家给你两个整数数组 energyDrinkA 和 energyDrinkB&#xff0c;数组长度都等于 n。这两个数组分别代表 A、B 两种不同能量饮料每小时所能提供的强化能量。 你需要每小时饮用一种能量饮料来 最大化 你的总强化能量。然而&#xff0c;如果从一种能量饮料切换到…

全国产 V7 690T+FT6678 高性能实时信号处理平台设计原理

1、概述 全国产 V7 690TFT6678 高性能实时信号处理平台组成如图 1 所示&#xff0c;包含 1 片SMQ7VX690TFFG1761 和两片 FT-6678&#xff08;国防科大&#xff09;的 DSP&#xff0c;总共 3 个主芯片&#xff1b;每个主芯片外部各搭配 1 组 64bit 的 DDR3 内存模组以及各芯片启…

0.STM32F1移植到F0的各种经验总结

1.结构体的声明需放在函数的最前面 源代码&#xff1a; /*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructu…

Linux高阶——1027—守护进程

1、守护进程的基本流程 1、父进程创建子进程&#xff0c;父进程退出 守护进程是孤儿进程&#xff0c;但是是工程师人为创建的孤儿进程&#xff0c;低开销模式运行&#xff0c;对系统没有压力 2、子进程&#xff08;守护进程&#xff09;脱离控制终端&#xff0c;创建新会话 …

Selective Generation for Language Models 语言模型的选择性生成

生成式语言模型&#xff08;Generative Language Models, GLMs&#xff09;在文本生成任务中取得了显著进展。然而&#xff0c;生成内容的“幻觉”现象&#xff0c;即生成内容与事实或真实语义不符的问题&#xff0c;仍是GLMs在实际应用中的一个重大挑战。为了解决这一问题&…

-bash: ./my_rename.sh: /bin/bash^M: bad interpreter: No such file or directory

在windows上写了一个shell脚本&#xff0c;在Linux上执行时报错&#xff0c;然后看下解决办法&#xff1a; 查了下&#xff0c;其实就是windows系统里文件行尾的换行符和Linux不同引起的&#xff0c; sed -i s/\r$// my.sh用这行代码处理一下&#xff0c;就可以正常运行了。 执…

flutter区别于vue的写法

View.dart 页面渲染&#xff1a; 类似于vue里面使用 <template> <div> <span> <textarea>等标签绘制页面, flutter 里面则是使用不同的控件来绘制页面 样式 与传统vue不同的是 flutter里面没有css/scss样式表&#xff0c; Flutter的理念是万物皆…

idea免费安装步骤,(java集成开发环境)超详细

第一步 点击链接下载 百度网盘 请输入提取码 提取码是idea 下载步骤 可设也可不设置 我就没有设置 下一步 就点击安装就大功告成了

SAP RFC 用户安全授权

一、SAP 通讯用户 对于RFC接口的用户&#xff0c;使用五种用户类型之一的“通讯”类型&#xff0c;这种类型的用户没有登陆SAPGUI的权限。 二、对调用的RFC授权 在通讯用户内部&#xff0c;权限对象&#xff1a;S_RFC中&#xff0c;限制进一步可以调用的RFC函数授权&#xff…

大数据-201 数据挖掘 机器学习理论 - 决策树 局部最优 剪枝 分裂 二叉分裂

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

计算机网络-总线型以太网(ethernet)-知识点小结

广域网与局域网区分: 广域网: 广域网不使用局域网技术, 传输介质 主要是光纤和电话线 常见广域网技术 综合业务数字网&#xff08;ISDN&#xff09;、 帧中继&#xff08;Frame Relay&#xff09;、 异步传输模式 局域网: 以太网--ethernet 简介: 是一种总线型局域网技术&#…