1、概述

  我们先来看两种模式的通用类图。

  两者之间确实很相似。如果把策略模式的环境角色变更为一个抽象类加一个实现类,或者桥梁模式的抽象角色未实现,只有修正抽象化角色,想想看,这两个类图有什么地方不一样?完全一样!正是由于类似场景的存在才导致了两者在实际应用中经常混淆的情况发生,我们来举例说明两者有何差别。

  大家都知道邮件有两种格式:文本邮件(TextMail)和超文本邮件(HTMLMaiL),在文本邮件中只能有简单的文字信息,而在超文本邮件中可以有复杂文字(带有颜色、字体等属性)、图片、视频等,如果你使用Foxmail邮件客户端的话就应该有深刻体验,看到一份邮件,怎么没内容?原来是你忘记点击那个“HTML邮件”标签了。下面我们就来讲解如何发送这两种不同格式的邮件,研究一下这两种模式如何处理这样的场景。

2、策略模式实现邮件发送

2.1 类图

  使用策略模式发送邮件,我们认为这两种邮件是两种不同的封装格式,给定了发件人、收件人、标题、内容的一封邮件,按照两种不同的格式分别进行封装,然后发送之。按照这样的分析,我们发现邮件的两种不同封装格式就是两种不同的算法,具体到策略模式就是两种不同策略,这样看已经很简单了,我们可以直接套用策略模式来实现。

  我们定义了一个邮件模板,它有两个实现类:TextMail(文本邮件)和HtmlMail(超文本邮件),分别实现两种不同格式的邮件封装。MailServer是一个环境角色,它接收一个MailTemplate对象,然后通过sendMail方法发送出去。

2.2 代码

2.2.1 抽象邮件

class CMailTemplate
{
public:
CMailTemplate(const string &sFrom, const string &sTo, const string &sSubject , const string &sContent) :
msFrom(sFrom), msTo(sTo), msSubject(sSubject), msContent(sContent){} ~CMailTemplate(){}; void mvSetFrom(const string &sFrom) { msFrom = sFrom; }
string msGetFrom() { return msFrom; } void mvSetTo(const string &sTo) { msTo = sTo; }
string msGetTo() { return msTo; } void mvSetSubject(const string &sSubject) { msSubject = sSubject; }
string msGetSubject() { return msSubject; } void mvSetContent(const string &sContent) { msContent = sContent; }
virtual string msGetContent(){ return msContent; } private:
string msFrom; //邮件发件人
string msTo; //收件人
string msSubject; //邮件标题
string msContent; //通过构造函数传递邮件信息
};

  抽象类没有抽象的方法,设置为抽象类还有什么意义呢?有意义,在这里我们定义了一个这样的抽象类:它具有邮件的所有属性,但不是一个具体可以被实例化的对象。例如,你对邮件服务器说“给我制造一封邮件”,邮件服务器肯定拒绝,为什么?你要产生什么邮件?什么格式的?邮件对邮件服务器来说是一个抽象表示,是一个可描述但不可形象化的事物。你可以这样说:“我要一封标题为XX,发件人是XXX的文本格式的邮件”,这就是一个可实例化的对象,因此我们的设计就产生了两个子类以具体化邮件,而且每种邮件格式对邮件的内容都有不同的处理。

2.2.2 文本邮件

class CTextMail : public CMailTemplate
{
public:
CTextMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
CMailTemplate(sFrom, sTo, sSubject, sContent) {} ~CTextMail() {} string msGetContent()
{
//文本类型设置邮件的格式为: text/plain
string s_content = "\nContent-Type: text/plain;charset=GB2312\n" + CMailTemplate::msGetContent();
//同时对邮件进行base64编码处理,这里用一句话代替
s_content += "\n邮件格式为: 文本格式"; return s_content;
}
};

  我们覆写了msGetContent方法,因为要把一封邮件设置为文本邮件必须加上一个特殊的标志:text/plain,用于告诉解析这份邮件的客户端:“我是一封文本格式的邮件,别解析错了”。

2.2.3 超文本邮件

  同样,超文本格式的邮件也有类似的设置。

class CHtmlMail : public CMailTemplate
{
public:
CHtmlMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
CMailTemplate(sFrom, sTo, sSubject, sContent) {} ~CHtmlMail() {} string msGetContent()
{
//超文本类型设置邮件的格式为: multipart/mixed
string s_content = "\nContent-Type: multipart/mixed; charset= GB2312\n" + CMailTemplate::msGetContent();
//同时对邮件进行base64编码处理,这里用一句话代替
s_content += "\n邮件格式为: 超文本格式"; return s_content;
}
};

  优秀一点的邮件客户端会对邮件的格式进行检查,比如编写一封超文本格式的邮件,在内容中加上了<font>标签,但是遗忘了</font>结尾标签,邮件的产生者(也就是邮件的客户端)会提示进行修正,我们这里用了“邮件格式为:超文本格式”来代表该逻辑。两个实现类实现了不同的算法,给定相同的发件人、收件人、标题和内容可以产生不同的邮件信息。

2.2.4 邮件服务器

class CMailServer
{
public:
CMailServer(CMailTemplate *opMail) : mopMail(opMail) {}
~CMailServer(){} //发送邮件
void mvSendMail()
{
cout << "====正在发送的邮件信息====" << endl;
//发件人
cout << "发件人: " << mopMail->msGetFrom().c_str() << endl;
//收件人
cout << "收件人:" << mopMail->msGetTo().c_str() << endl;
//标题
cout << "邮件标题: " << mopMail->msGetSubject().c_str() << endl;
//邮件内容
cout << "邮件内容: " << mopMail->msGetContent().c_str() << endl;
} private:
//发送的是哪封邮件
CMailTemplate *mopMail;
};

  很简单,邮件服务器接收了一封邮件,然后调用自己的发送程序进行发送。有人可能要问了,为什么不把mvSendMail方法移植到邮件模板类中呢?这也是邮件模板类的一个行为,邮件可以被发送。是的,这确实是邮件的一个行为,完全可以这样做,两者没有什么区别,只是从不同的角度看待该方法而已。

2.2.5 场景调用

int main()
{
//创建一封TEXT格式的邮件
CMailTemplate *op_mail = new CHtmlMail("a@a.com", "b@b.com", "外星人攻击地球了", "结果是外星人被地球人打败了!"); //创建一个Mail发送程序
CMailServer *op_server = new CMailServer(op_mail);
op_server->mvSendMail(); return ;
}

2.2.6 执行结果

  当然,如果想产生一封文本格式的邮件,只要稍稍修改一下场景类就可以了:new CHtmlMail修改为new CTextMail,非常简单。在该场景中,我们使用策略模式实现两种算法的自由切换,它提供了这样的保证:封装邮件的两种行为是可选择的,至于选择哪个算法是由上层模块决定的。策略模式要完成的任务就是提供两种可以替换的算法。

3、桥梁模式实现邮件发送

3.1 类图

  桥梁模式关注的是抽象和实现的分离,它是结构型模式,结构型模式研究的是如何建立一个软件架构,下面我们就来看看桥梁模式是如何构件一套发送邮件的架构的。

  类图中我们增加了SendMail和Postfix两个邮件服务器来实现类,在邮件模板中允许增加发送者标记,其他与策略模式都相同。我们在这里已经完成了一个独立的架构,邮件有了,发送邮件的服务器也具备了,是一个完整的邮件发送程序。需要注意的是,SendMail类不是一个动词行为(发送邮件),它指的是一款开源邮件服务器产品,一般*nix系统的默认邮件服务器就是SendMail;Postfix也是一款开源的邮件服务器产品,其性能、稳定性都在逐步赶超SendMail。

3.2 代码

3.2.1 邮件模板

  我们来看代码实现,邮件模板仅仅增加了一个add方法,文本邮件、超文本邮件都没有任何改变。

//邮件模板
class CMailTemplate
{
public:
CMailTemplate(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
msFrom(sFrom), msTo(sTo), msSubject(sSubject), msContent(sContent){} ~CMailTemplate(){}; void mvSetFrom(const string &sFrom) { msFrom = sFrom; }
string msGetFrom() { return msFrom; } void mvSetTo(const string &sTo) { msTo = sTo; }
string msGetTo() { return msTo; } void mvSetSubject(const string &sSubject) { msSubject = sSubject; }
string msGetSubject() { return msSubject; } void mvSetContent(const string &sContent) { msContent = sContent; }
virtual string msGetContent(){ return msContent; } //允许增加邮件发送标志
void mvAdd(const string &sSendInfo)
{
msContent = sSendInfo + msContent;
} private:
string msFrom; //邮件发件人
string msTo; //收件人
string msSubject; //邮件标题
string msContent; //通过构造函数传递邮件信息
}; //文本邮件
class CTextMail : public CMailTemplate
{
public:
CTextMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
CMailTemplate(sFrom, sTo, sSubject, sContent) {} ~CTextMail() {} string msGetContent()
{
//文本类型设置邮件的格式为: text/plain
string s_content = "\nContent-Type: text/plain;charset=GB2312\n" + CMailTemplate::msGetContent();
//同时对邮件进行base64编码处理,这里用一句话代替
s_content += "\n邮件格式为: 文本格式"; return s_content;
}
}; //超文本邮件
class CHtmlMail : public CMailTemplate
{
public:
CHtmlMail(const string &sFrom, const string &sTo, const string &sSubject, const string &sContent) :
CMailTemplate(sFrom, sTo, sSubject, sContent) {} ~CHtmlMail() {} string msGetContent()
{
//超文本类型设置邮件的格式为: multipart/mixed
string s_content = "\nContent-Type: multipart/mixed; charset= GB2312\n" + CMailTemplate::msGetContent();
//同时对邮件进行base64编码处理,这里用一句话代替
s_content += "\n邮件格式为: 超文本格式"; return s_content;
}
};

3.2.2 邮件服务器

  我们来看邮件服务器,也就是桥梁模式的抽象化角色。

class CMailServer
{
public:
CMailServer(CMailTemplate *opMail) : mopMail(opMail) {};
~CMailServer(){}; //发送邮件
virtual void mvSendMail()
{
cout << "====正在发送的邮件信息====" << endl;
//发件人
cout << "发件人:" << mopMail->msGetFrom().c_str() << endl;
//收件人
cout << "收件人:" << mopMail->msGetTo().c_str() << endl;
//标题
cout << "邮件标题: " << mopMail->msGetSubject().c_str() << endl;
//邮件内容
cout << "邮件内容: " << mopMail->msGetContent().c_str() << endl;
} protected:
//发送的是哪封邮件
CMailTemplate *mopMail;
};

  该类相对于策略模式的环境角色有两个改变:

  ● 修改为抽象类。为什么要修改成抽象类?因为我们在设计一个架构,邮件服务器是一个具体的、可实例化的对象吗?“给我一台邮件服务器”能实现吗?不能,只能说“给我一台Postfix邮件服务器”,这才能实现,必须有一个明确的可指向对象。

  ● 变量m修改为Protected访问权限,方便子类调用。

3.2.3 Postfix邮件服务器

class CPostfix : public CMailServer
{
public:
CPostfix(CMailTemplate *opMail) : CMailServer(opMail) {}
~CPostfix(){} //修正邮件发送程序
void mvSendMail()
{
//增加邮件服务器信息
string s_content = "Received: from XXXX (unknown [xxx.xxx.xxx.xxx]) by aaa.aaa.com(Postfix) with ESMTP id 8DBCB1456B8\n";
mopMail->mvAdd(s_content);
CMailServer::mvSendMail();
}
};

3.2.4 SendMail邮件服务器

  为什么要覆写mvSendMail程序呢?这是因为每个邮件服务器在发送邮件时都会在邮件内容上留下自己的标志,一是广告作用,二是为了互联网上统计需要,三是方便同质软件的共振。

class CSendMail : public CMailServer
{
public:
CSendMail(CMailTemplate *opMail) : CMailServer(opMail) {}
~CSendMail(){} //修正邮件发送程序
void mvSendMail()
{
//增加邮件服务器信息
string s_content = "Received: (sendmail); 7 Nov 2009 04:14:44 +0100";
mopMail->mvAdd(s_content);
CMailServer::mvSendMail();
}
};

3.2.5 场景调用

  邮件和邮件服务器都有了,我们来看怎么发送邮件。

int main()
{
//创建一封TEXT格式的邮件
CMailTemplate *op_mail = new CTextMail("a@a.com", "b@b.com", "外星人攻击地球了", " 结果地球人打败了外星人!");
//使用Postfix发送邮件
CMailServer *op_post = new CPostfix(op_mail);
//发送邮件
op_post->mvSendMail(); return ;
}

3.2.6 运行结果

  当然了,还有其他三种发送邮件的方式:Postfix发送文本邮件以及SendMail发送文本邮件和超文本邮件。

4、总结

  策略模式和桥梁模式是如此相似,我们只能从它们的意图上来分析。策略模式是一个行为模式,旨在封装一系列的行为,在例子中我们认为把邮件的必要信息(发件人、收件人、标题、内容)封装成一个对象就是一个行为,封装的格式(算法)不同,行为也就不同。而桥梁模式则是解决在不破坏封装的情况下如何抽取出它的抽象部分和实现部分,它的前提是不破坏封装,让抽象部分和实现部分都可以独立地变化,在例子中,我们的邮件服务器和邮件模板是不是都可以独立地变化?不管是邮件服务器还是邮件模板,只要继承了抽象类就可以继续扩展,它的主旨是建立一个不破坏封装性的可扩展架构。

  简单来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。桥梁模式必然有两个“桥墩”——抽象化角色和实现化角色,只要桥墩搭建好,桥就有了,而策略模式只有一个抽象角色,可以没有实现,也可以有很多实现。

  还是很难区分,是吧?多想想两者的意图,就可以理解为什么要建立两个相似的模式了。我们在做系统设计时,可以不考虑到底使用的是策略模式还是桥梁模式,只要好用,能够解决问题就成,“不管黑猫白猫,抓住老鼠的就是好猫”。

【设计模式】 模式PK:策略模式VS桥梁模式的更多相关文章

  1. 行为型设计模式之模板方法(TEMPLATE METHOD)模式 ,策略(Strategy )模式

    1 模板方法(TEMPLATE METHOD)模式: 模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类.子 ...

  2. 策略模式 VS 桥梁模式

    这对冤家终于碰头了,策略模式与桥梁模式是如此相似,简直就是孪生兄弟,要把它们两个分开需要花费大量智力,我们来看看两者的通用类图,如图33-1所示. 图33-1 策略模式(左)和桥梁模式(右)通用类图 ...

  3. 说说设计模式~桥梁模式(Bridge)

    返回目录 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度? ...

  4. JAVA设计模式之桥梁模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...

  5. 24种设计模式--桥梁模式【Bridge Pattern】

    今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造业,这两个公司都很赚钱,天天帮我在累加财富,其实是什么公司我倒是不关心,我关心的是是不是在赚钱,赚了多少, ...

  6. JavaScript设计模式-11.桥梁模式

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. 设计模式 桥梁模式 JDBC

    桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation) ...

  8. 第 11 章 桥梁模式【Bridge Pattern】

    以下内容出自:<<24种设计模式介绍与6大设计原则>> 今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造业,这两个公司都很赚钱,天 ...

  9. 桥梁模式(Bridge Pattern)

    桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation) ...

  10. 《JAVA与模式》之桥梁模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...

随机推荐

  1. POJ 2987 Firing(最大流最小割の最大权闭合图)

    Description You’ve finally got mad at “the world’s most stupid” employees of yours and decided to do ...

  2. MySQL用户管理及权限管理

    MySQL 默认有个root用户,但是这个用户权限太大,一般只在管理数据库时候才用.如果在项目中要连接 MySQL 数据库,则建议新建一个权限较小的用户来连接. 在 MySQL 命令行模式下输入如下命 ...

  3. Thinkphp实现excel导出数据

    前端: 点击导出触发click事件,传值export指令和args关键字(args是指我们是否有查询取哪些数据)到控制器 $(document).on("click", " ...

  4. 《C++常见问题及解答》

    一.类 1. 常数据成员的初始化只能在构造函数的初始化列表中进行 2. 静态数据成员不可以在类内初始化 3. 创建一个对象时的构造函数的调用次序:对象成员的构造函数.对象自身的构造函数 4. 创建一个 ...

  5. 你代码写得这么丑,一定是因为你长得不好看----panboo第一篇博客

    一.个人介绍 我叫潘博,软嵌162,学号1613072055. 以“panboo”名称混迹于各大开源IT论坛与博客. 除了编程,我的最大爱好是篮球与健身,热衷于各种IT技术与运动. 我做过的软件项目有 ...

  6. Alpha冲刺——第三天

    Alpha第三天 听说 031502543 周龙荣(队长) 031502615 李家鹏 031502632 伍晨薇 031502637 张柽 031502639 郑秦 1.前言 任务分配是VV.ZQ. ...

  7. 解决XAMPP中,MYSQL因修改my.ini后,无法启动的问题

    论这世上谁最娇贵,不是每年只开七天的睡火莲,也不是瑞典的维多利亚公主,更不是一到冬天就自动关机的iPhone 6s, 这世上最娇贵的,非XAMPP中的mysql莫属,记得儿时的我,年少轻狂,当时因为m ...

  8. 初入py

    1.下载工具sublime 我的网盘下载地址:https://pan.baidu.com/s/18-U1ZSg_zHoSAqUuvXj_PQ 直接解压即可 2.配置py27 在新建的文件里面编辑并保存 ...

  9. WPF一个对象显示多个属性

    一个对象显示多个属性使用模板的方法: 如图: <dataTemplate x:key="MyDataTemplate">

  10. BZOJ5466 NOIP2018保卫王国(倍增+树形dp)

    暴力dp非常显然,设f[i][0/1]表示i号点不选/选时i子树内的答案,则f[i][0]=Σf[son][1],f[i][1]=a[i]+Σmin(f[son][0],f[son][1]). 注意到 ...