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.rhkb.cn/news/462405.html

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

相关文章

Rust 力扣 - 48. 旋转图像

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们可以将原矩阵进行水平翻转&#xff0c;然后在沿主对角线进行翻转&#xff0c;就能完成原矩阵沿顺时针方向旋转90o的变换 题解代码 impl Solution {pub fn rotate(matrix: &mut Vec<Vec<i32>&…

SQL 基础语法(一)

文章目录 1. SQL 分类2. 数据库操作3. 数据表操作4. 增删改操作5. 查询操作6. 用户管理7. 权限控制 1. SQL 分类 2. 数据库操作 #创建数据库 create database if not exists test;#查询所有数据库 show databases;#查询当前数据库 select database();#删除数据库 drop databas…

高效水电管理:Spring Boot在大学城的应用

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理大学城水电管理系统的相关信息成为必然。开…

轻松理解操作系统 - Linux 文件系统的心脏是什么?

在前面两期&#xff0c;我们分别了解了 Linux 文件系统的重要组成部分&#xff1a;inode 和 数据块。 那 inode表 和 数据块 的信息总要有个“管理者”来进行总体的管理和提供找到它们的入口吧&#xff0c;这时候“超级块”就扮演了这个“管理者”的角色。 一、文件系统的“管家…

python之字符串总结

字符串&#xff08;str&#xff09; 对于字符串的学习&#xff0c;我整理了网上的一些资料&#xff0c;希望可以帮助到各位&#xff01;&#xff01;&#xff01; 概述 由多个字母&#xff0c;数字&#xff0c;特殊字符组成的有限序列 字符串的定义&#xff1a;可以使用一对…

操作符习题练习

1.计算输入一个数的二进制中1的个数 方法一&#xff1a; #include<stdio.h> void Count(int n) {int count 0;int i 0;int tmp 0;for (i 1;i < 64;i)//这里是在64位环境下编码进行的&#xff0c;如果是在32位环境下&#xff0c;需要将循环次数改为32{tmp n &am…

DataFlow v202410 版本更新 一站式数据处理平台

DataFlow 是 OpenCSG 推出的一站式数据处理平台&#xff0c;与 CSGHub 无缝集成&#xff0c;形成数据到模型的全生命周期闭环&#xff0c;助力持续优化。平台兼容多种数据格式与来源&#xff0c;支持本地、云端和网络数据接入&#xff0c;并提供高效转换和读取工具&#xff0c;…

mysql上课总结(5)(MySQL的完整性约束(详细介绍))

目录 一、完整性约束。 &#xff08;1&#xff09;概念与目的。 <1>概念。 <2>目的。 &#xff08;2&#xff09;各个约束的详细&#xff08;表格&#xff09; &#xff08;3&#xff09;各个约束的简要总结。 <1>主键约束。 <2>唯一约束。 <3>非…

yarn install 出现 error Error: certificate has expired

接手老项目&#xff0c;yarn install的时候出现error Error: certificate has expired&#xff0c;提示证书过期了&#xff0c;查看yarn.lock&#xff0c;因为存在yarn.lock的项目执行yarn install是它里面去执行下载固定版本的包。 查看后发现之前一直在使用的是https://regis…

WPF+MVVM案例实战(十九)- 自定义字体图标按钮的封装与实现(EF类)

文章目录 1、案例效果1、按钮分类2、E类按钮功能实现与封装1.文件创建与代码实现2、样式引用与封装 3、F类按钮功能实现与封装1、文件创建与代码实现2、样式引用与封装 3、按钮案例演示1、页面实现与文件创建2、运行效果如下 4、源代码获取 1、案例效果 1、按钮分类 在WPF开发…

pandas——DataFrame

一、dataframe &#xff08;一&#xff09;创建dataframe file.csv Name,Age,City Alice,30,New York Bob,25,Los Angeles Charlie,35,Chicagoimport pandas as pd 1.使用字典创建DataFrame&#xff1a; 其中字典的键是列名&#xff0c;值是数据列表。print(1.使用字典创建D…

Maven项目的基础配置:利用IDEA将SpringBoot的项目打包成war文件

文章目录 引言Maven项目的聚合与继承(依赖管理)把项目打包成war包其他打包配置引言 利用IDEA将SpringBoot的项目打包成war文件Maven项目的聚合与继承(依赖管理)Maven项目的聚合与继承(依赖管理) 把项目打包成war包 利用IDEA将SpringBoot的项目打包成war文件:要配置启动…

Vue3+TypeScript+Vite 后台管理项目_登录页面开发实战

一、前言 基于之前创建的基础工程&#xff0c;接下来我们完成登录页面的开发。 https://blog.csdn.net/qq_34709175/article/details/143426433?spm1001.2014.3001.5501 这里需要交代一下&#xff0c;项目里的文件命名规则&#xff0c;以及文件结构&#xff0c;views下存放…

【销帮帮-注册/登录安全分析报告-试用页面存在安全隐患】

联通支付注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨…

文件管理软件根据多个关键字将不同目录下的文件夹批量复制或移动到新的指定文件夹,完成大量文件夹和文件管理任务

在浩瀚的数字海洋中&#xff0c;文件夹如同散落的珍珠&#xff0c;等待着被有序地串连。首助编辑高手软件&#xff0c;携带着其独特的按多关键字分发功能&#xff0c;犹如一位智慧的渔夫&#xff0c;能够精准地捕捉那些含有特定关键字的文件夹&#xff0c;并将它们从各个角落批…

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

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

SpringBoot接入星火认知大模型

文章目录 准备工作整体思路接入大模型服务端和大模型连接客户端和服务端的连接测试 准备工作 到讯飞星火大模型上根据官方的提示申请tokens 申请成功后可以获得对应的secret&#xff0c;key还有之前创建的应用的appId&#xff0c;这些就是我们要用到的信息 搭建项目 整体思…

新老项目不同node版本,使用nvm控制node版本切换(mac、window)

window系统电脑的链接&#xff1a;https://blog.csdn.net/qq_40269801/article/details/136450961 以下是mac版本的操作方式&#xff1a; 1、打开终端 克隆 NVM 仓库&#xff1a; git clone https://github.com/nvm-sh/nvm.git ~/.nvm 2、运行安装脚本&#xff1a; cd ~/.n…

kafka如何获取 topic 主题的列表?

大家好&#xff0c;我是锋哥。今天分享关于【kafka如何获取 topic 主题的列表&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka如何获取 topic 主题的列表&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;可以…

半参数模型

4. 半参数模型 (Semi-parametric Model) 半参数模型结合了参数化和非参数化的方法。可以在整体上采用线性回归&#xff0c;但在局部允许非线性变化。这样做的目的是在保持模型的线性解释性的同时&#xff0c;捕捉细微的弧度趋势。 例如&#xff0c;可以定义&#xff1a; y …