【设计模式】 模式PK:策略模式VS桥梁模式
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桥梁模式的更多相关文章
- 行为型设计模式之模板方法(TEMPLATE METHOD)模式 ,策略(Strategy )模式
1 模板方法(TEMPLATE METHOD)模式: 模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类.子 ...
- 策略模式 VS 桥梁模式
这对冤家终于碰头了,策略模式与桥梁模式是如此相似,简直就是孪生兄弟,要把它们两个分开需要花费大量智力,我们来看看两者的通用类图,如图33-1所示. 图33-1 策略模式(左)和桥梁模式(右)通用类图 ...
- 说说设计模式~桥梁模式(Bridge)
返回目录 在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度? ...
- JAVA设计模式之桥梁模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...
- 24种设计模式--桥梁模式【Bridge Pattern】
今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造业,这两个公司都很赚钱,天天帮我在累加财富,其实是什么公司我倒是不关心,我关心的是是不是在赚钱,赚了多少, ...
- JavaScript设计模式-11.桥梁模式
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 设计模式 桥梁模式 JDBC
桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation) ...
- 第 11 章 桥梁模式【Bridge Pattern】
以下内容出自:<<24种设计模式介绍与6大设计原则>> 今天我要说说我自己,梦想中的我自己,我身价过亿,有两个大公司,一个是房地产公司,一个是服装制造业,这两个公司都很赚钱,天 ...
- 桥梁模式(Bridge Pattern)
桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation) ...
- 《JAVA与模式》之桥梁模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...
随机推荐
- Linux 添加虚拟网卡
使用的Linux版本是Centos 7: [root@vnode33 bin]# cat /etc/redhat-release CentOS Linux release (Core) 使用ifcon ...
- C语言 结构体相关 函数 指针 数组
. 作者 : 万境绝尘 转载请注明出处 : http://www.hanshuliang.com/?post=30 . 结构体概述 : 结构体是 多个 变量的集合, 变量的类型可以不同; -- 可进行 ...
- iOS-UILabel加线
NSAttributedString *attrStr =[[NSAttributedString alloc]initWithString:[NSString stringWithFormat:], ...
- TCP系列06—连接管理—5、TCP fastopen(TFO)
一.TFO背景 当前web和web-like应用中一般都是在三次握手后开始数据传输,相比于UDP,多了一个RTT的时延,即使当前很多应用使用长连接来处理这种情况,但是仍然由一定比例的短连接,这额外多出 ...
- 3ds Max学习日记(八)
再来更新一波学习进度. 之前玩了一下3dsmax里的灯光,不过由于和教程里的版本不同,教程里的我的没有,我有的教程又没有,所以只能瞎jb玩一玩. 最近又想建个人物模型玩玩,于是上网搜一下有 ...
- 原生js操作Dom节点:CRUD
知识点,依然会遗忘.我在思考到底是什么原因.想到研究生考试准备的那段岁月,想到知识体系的建立,知识体系分为正向知识体系和逆向知识体系:正向知识体系可以理解为教科书目录,逆向知识体系可以理解考试真题. ...
- BZOJ 1305 跳舞(二分+网络流)
无法直接构造最大流来解决这个问题,因为题目要求每首舞曲都需要n对男女进行跳舞. 答案又满足单调性,这启发我们二分答案,判断是否满流验证答案. 假设舞曲数目为x时满足条件,那么每个男生和女生都需要跳x次 ...
- 【bzoj3545/bzoj3551】[ONTAK2010]Peaks/加强版 Kruskal+树上倍增+Dfs序+主席树
bzoj3545 题目描述 在Bytemountains有N座山峰,每座山峰有他的高度h_i.有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询 ...
- sql批量更新关系型数据库
更改gb_groupd里的chargingrulesname的值UPDATE tb_group SET tb_group.chargingrulesname =tb_chargingrules.c ...
- [洛谷P3181][HAOI2016]找相同字符
题目大意:给你两个字符串,求从两个字符串中各选择一个字串使得这两个字串相同的方案数. 题解:建广义$SAM$,对每个点求出在第一个串中出现次数和第二个串中出现次数,乘起来就行了 卡点:无 C++ Co ...