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.mzph.cn/news/442965.shtml

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

相关文章

c99什么意思_C语言中-是什么意思啊?

展开全部->是一个整体,它是用于指向结构体、C中的class等含有子数据的指针用e5a48de588b662616964757a686964616f31333366303130来取子数据。换种说法,如果我们在C语言中定义了一个结构体,然后申明一个指针指向这个结构体,那么…

mysql排序加权_mysql多关键词查询相关匹配加权排序

SELECT id, title, SUM((CASE WHEN title LIKE %复仇者% THEN 1 ELSE 0 END)(CASE WHEN title LIKE %联盟% THEN 1 ELSE 0 END)) as rnt FROM hash WHERE title LIKE %联盟% group by id ORDER by rnt DESC关键1.就是这个SUM()求和函数关键2.该SUM函数要搭配子句:gr…

mysql外键关联查询_MySQL外键约束和多表联查

一.创建外键#测试数据表# 教师表,主表CREATE TABLE teacher(id INT PRIMARY KEY AUTO_INCREMENT,name varchar(20),age INT)ENGINEInnoDB DEFAULT CHARSETUTF8;#测试数据:INSERT INTO teacher values(1,范冰冰,22),(2,周杰棍,30),(3,双杰伦,35),(4,梁朝伟…

mysql innodb id_MySQL InnoDB row_id边界溢出验证的方法步骤

背景跟同学聊到row_id一个边界问题,这里详细说明下。InnoDB表若没有定义主键,会使用系统的一个默认递增row_id (dict_sys->row_id)作为主键。每次插入一行加1,到达最大值循环复用。需要注意的是,虽然dict_sys->row_id 被定义…

mysql memcached java_java缓存技术memcached实例

1 下载memcached-1.2.1-win32.zip 并且解压.2 新建web project项目 名字自己取,然后导入必要的包,alisoft-xplatform-asf-cache-2.5.1.jar,commons-logging-1.0.3.jar,log4j-1.2.13.jar3 新建类 user.javapackage bean;import java.io.Serializable;public class User impleme…

编译安装mysql5.6.36_MySQL5.6.36编译安装

一、MySQL5.6.36安装前准备(1)克隆一个模板机器(使用centos6),克隆完做快照(2)IP 10.0.0.52 主机名db02(3)iptables selinux(4)下载好5.6.36(5)安装依赖包yum install -y ncurses-devel libaio-devel cmake(6)安装cmakeyum install cmake –y(7)创建用户useradd -s …

mysql mysqld.sock_MySQL笔记-最简单的方法来解决找不到mysqld.sock文件的问题

首先,环境:ubuntu 14.04,采用apt-get的方式安装的,手动安装可能路径设置稍有区别。1、安装MySQL后,用命令行首次启动时发现找不到Mysqld.sock文件,提示:ERROR 2002 (HY000): Cant connect to local MySQL server throu…

plan explorer mysql_plan explorer支持oracle吗

展开全部1.SQL语句的执行62616964757a686964616fe58685e5aeb931333361326365计划使用EXPLAIN PLAN语句来确定Oracle数据库下指定SQL语句的执行计划,这个语句插入每一步执行计划的行描述到指定表中。你也可使用EXPLAIN PLAN语句作为SQL跟踪工具的一部分。EXPLAIN PLA…

python 列表转图结构_Python读取网络(图)边列表数据进而转化为邻接矩阵

import networkx as nxG nx.Graph()path ./edge_list.txtedge_list []node_set set() #集合的特性就是元素不会重复,互异的with open(path, r) as f:for line in f:cols line.strip().split( )y1int(cols[0])y2int(cols[1])node_set.add(y1)node_set.add(y2)ed…

r语言读写word_R语言:在word中插入ggplot

最近CRAN上新了一个叫eoffice的package,并且不时被各路大佬提起。这个包的功能刚好也符合我最近的需求,这次带各位先来试试水。包的官方介绍:1. Introduction​cran.r-project.org这次主要试试在word中用该包插入ggplot。既然要试&#xff0c…

hamburger组件_一个侧边栏导航组件实现思路

翻译:布兰作者:Adam Argyle来源:https://web.dev/building-a-sidenav-component/在这篇文章中,我想和大家分享我是如何为 web 原型化一个 Sidenav 组件的,这个组件是响应式的,有状态的,支持键盘…

centos php mysql 5.6 安装_centos7安装nginx、php5.5、mysql5.6

一、nginx1、安装yum install nginx2、启动systemctl start nginx关闭:systemctl stop nginx 重启:systemctl restart nginx 检查状态:systemctl status nginx3、测试浏览器直接访问http://ip,应该会看到以下界面:4、支持php打开/…

myeclipse怎么导入mysql驱动_myeclipse sql导入数据库驱动包

如何配置strutshibernate,基本使用方法不少童鞋在自学SSH框架的时候,难在创建第一个项目,如何搭建好这些框架,很多书上只是给出了代码但是没有教如何使用,所以在本次博客中将会图文结合来说一下如何使用struts结合hibe…

mysql 报表统计sql使用实例_mysql 案例~mysql元数据的sql统计

一 简介:今天我们来收集下提取元数据的sql二 前沿: information_schema 引擎 memory 元数据收集表三 sql语句:1#没有使用索引的表统计SELECT t.TABLE_SCHEMA,t.TABLE_NAME,t.TABLE_ROWS FROM information_schema.tables AS t LEFT JOIN (SELECT DISTINCT table_schema, table_…

创建或更改表 tablename 失败_mysql 创建用户

一. 创建用户命令:CREATE USER usernamehost IDENTIFIED BY password;说明:username:你将创建的用户名host:指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost,如果想让该用户可以从任意远程主机登陆&#…

linux忘记mysql登录用户密码_linux中忘记mysql用户root密码解决方案

1.vim /etc/my.cnf[mysqld]skip-grant-tables ##追加此行,跳过权限表,2.重启mysqlsystemctl restart mysqld3.mysql 登陆mysqlmysql> use mysql;mysql> UPDATE user SET Password password ( ‘zha123456‘ ) WHERE User ‘root‘ ;mysql> fl…

sql2000 mysql 兼容_SQL Server2000如何恢复数据库

以里诺仓库管理软件(SQL网络版)为例,如果您因电脑操作系统重装,需要把以前备份的数据库恢复过来,请您按如下操作来。首先,您需要安装MS SQL Server2000。1. 以Windowns XP为例,SQL Server个人版安装完成后,…

suse下删除mysql_每日MySQL之005:SUSE linux下卸載MySQL

卸載這里的卸載,對應於之前的安裝停止MySQL服務:db2a:~ #service mysql stop找到所有的MySQL包:db2a:~ #rpm -qa | grep -i mysqlmysql-community-server-5.7.19-1.sles11mysql-community-common-5.7.19-1.sles11libqt4-sql-mysql-4.6.3-5.34…

java中br.readline_Java:java中BufferedReader的read()及readLine()方法的使用心得

BufferedReader的readLine()方法是阻塞式的, 如果到达流末尾, 就返回null, 但如果client的socket末经关闭就销毁, 则会产生IO异常. 正常的方法就是使用socket.close()关闭不需要的socket.从一个有若干行的文件中依次读取各行,处理后输出,如果用以下方法&…

java dfs_Java数据结构与算法 深搜(DFS)的简单使用(一)之排列组合

今天,我们来简单介绍一下深度优先搜索(DFS)的概念和使用。在百度词条中,对深搜的解释是这样的。百度词条中的解释由此,我们可知,深搜是广泛运用到 图 中的搜索方法之一。用深度优先搜索遍历图的基本思路是:(1)访问顶点…