策略模式 VS 桥梁模式
这对冤家终于碰头了,策略模式与桥梁模式是如此相似,简直就是孪生兄弟,要把它们两个分开需要花费大量智力,我们来看看两者的通用类图,如图33-1所示。
图33-1 策略模式(左)和桥梁模式(右)通用类图
什么?你没有看出两者之间很相似?如果把策略模式的环境角色变更为一个抽象类加一个实现类,或者桥梁模式的抽象角色未实现,只有修正抽象化角色,想想看,这两个类图有什么地方不一样?一样,完全一样!正是由于类似场景存在才导致了两者在实际应用中经常混淆的情况发生,我们来举例说明两者有何差别。
大家应该都知道邮件有两种格式:文本邮件(Text Mail)和超文本邮件(HTML MaiL),在文本邮件中只能有简单的文字信息,而在超文本邮件中可以有复杂文字(带有颜色、字体等属性)、图片、视频等,如果你使用Foxmail邮件客户端的话就应该有深刻体验,看到一份邮件,嗯?怎么没内容,你忘记点击那个“HTML邮件”标签了。我们今天的例子就来讲解如何发送这两种不同格式的邮件,研究一下这两种模式是如何处理这样的场景。
33.1.1 策略模式发送邮件
使用策略模式发送邮件,我们认为这两种邮件是两种不同的封装格式,给定了发件人、收件人、标题、内容的一封邮件,按照两种不同的格式分别进行封装,然后发送之。按照这样分析,我们发现邮件的两种不同封装格式就是两种不同的算法,具体到策略模式就是两种不同策略,那这样已经很简单了,我们可以直接套用策略模式来实现,先看类图,如图33-2所示。
![]()
图33-2 策略模式实现邮件发送
我们定义了一个邮件模版,它有两个实现类:TextMail(文本邮件)和HtmlMail(超文本邮件),分别实现两种不同格式的邮件封装。MailServer是一个环境角色,它接受一个MailTemplate对象,然后通过sendMail方法发送出去。我们来看具体的代码,先看抽象邮件,如代码清单33-1所示。
代码清单33-1 抽象邮件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
public abstract class MailTemplate {//邮件发件人private String from;//收件人private String to;//邮件标题private String subject;//邮件内容private String context;//通过构造函数传递邮件信息public MailTemplate(String _from,String _to,String _subject,String _context){this.from = _from;this.to = _to;this.subject = _subject;this.context = _context;}public String getFrom() {return from;}public void setFrom(String from) {this.from = from;}public String getTo() {return to;}public void setTo(String to) {this.to = to;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public void setContext(String context){this.context = context;}//邮件都有内容public String getContext(){return context;}} |
很奇怪,是吗?抽象类怎么没有抽象的方法,设置为抽象类还有什么意义呢?有意义,在这里我们定义了一个这样的抽象类:它具有邮件的所有属性,但不是一个具体可以被实例化的对象。例如你对邮件服务器说“给我制造一封邮件”,邮件服务器肯定拒绝,为什么?你要产生什么邮件?什么格式的?邮件对邮件服务器来说是一个抽象表示,是一个可描述,但不可形象化的事物,你可以这样说“我要一封标题为XX,发件人是XXX的文本格式的邮件”,这就是一个可实例化的对象,因此我们的设计就产生了两个子类,以具体化邮件,而且每种邮件格式对邮件的内容都有不同的处理,我们首先看文本邮件,如代码清单33-2所示。
代码清单33-2 文本邮件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class TextMail extends MailTemplate {public TextMail(String _from, String _to, String _subject, String _context) {super(_from, _to, _subject, _context);}public String getContext() {//文本类型则设置邮件的格式为:text/plainString context = "\nContent-Type: text/plain;charset=GB2312\n" +super.getContext();//同时对邮件进行base64编码处理,这里用一句话代替context = context + "\n邮件格式为:文本格式";return context;}} |
我们覆写了getContext方法,因为要把一封邮件设置为文本邮件必须加上一个特殊的标志:text/plain,这句话是告诉解析这份邮件的客户端:“我是一封文本格式的邮件,别解析错了”。同样,超文本格式的邮件也有类似的设置,如代码清单33-3所示。
代码清单33-3 超文本邮件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class HtmlMail extends MailTemplate {public HtmlMail(String _from, String _to, String _subject, String _context) {super(_from, _to, _subject, _context);}public String getContext(){//超文本类型则设置邮件的格式为:multipart/mixedString context = "\nContent-Type: multipart/mixed;charset=GB2312\n"+super.getContext();//同时对邮件进行HTML检查,是否有类似未关闭的标签context = context + "\n邮件格式为:超文本格式";return context;}} |
优秀一点的邮件客户端会对邮件的格式进行检查,比如编写一封超文本格式的邮件,在内容中加上了<font>标签,但是遗忘了</font>结尾标签,邮件的产生者(也就是邮件的客户端)会提示进行修正,我们这里用了“邮件格式:超文本格式”来代表该逻辑。
两个实现类实现了不同的算法,给定相同的发件人、收件人、标题和内容可以产生不同的邮件信息。我们看看邮件是如何发送出去的,如代码清单33-4所示。
代码清单33-4 邮件服务器
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class MailServer {//发送的是哪封邮件private MailTemplate m;public MailServer(MailTemplate _m){this.m = _m;}//发送邮件public void sendMail(){System.out.println("====正在发送的邮件信息====");//发件人System.out.println("发件人:" + m.getFrom());//收件人System.out.println("收件人:" + m.getTo());//标题System.out.println("邮件标题:" + m.getSubject() );//邮件内容System.out.println("邮件内容:" + m.getContext());}} |
很简单,邮件服务器接受了一封邮件,然后调用自己的发送程序进行发送。可能各位读者要提问了,为什么不把sendMail方法移植到邮件模板类中呢?这也是邮件模板类的一个行为呀,邮件可以被发送,是的,确实是邮件的一个行为,完全可以这样做,两者没有什么特别的区别,只是从不同的角度看待该方法而已。我们继续看场景类,如代码清单33-5所示。
代码清单33-5 场景类
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class Client {public static void main(String[] args) {//创建一封TEXT格式的邮件MailTemplate m = new HtmlMail("a@a.com","b@b.com","外星人攻击地球了","结局是外星人被中国人熬汤炖着吃了!");//创建一个Mail发送程序MailServer mail = new MailServer(m);//发送邮件mail.sendMail();}} |
运行结果如下所示:
====正在发送的邮件信息====
发件人:a@a.com
收件人:b@b.com
邮件标题:外星人攻击地球了
邮件内容:
Content-Type: multipart/mixed;charset=GB2312
结局是外星人被中国人熬汤炖着吃了!
邮件格式为:超文本格式
当然了,你想产生了一封文本格式的邮件只要稍稍修改一下场景类就可以了:new HtmlMail修改为new TextMail,读者可以自行实现,非常简单。在该场景中,我们使用策略模式实现两种算法的自由切换,它提供了这样的保证:封装邮件的两种行为是可选择的,至于选择哪个算法则是由上层模块决定。策略模式要完成的任务就是提供两种可以替换的算法。
33.1.2 桥梁模式实现邮件发送
桥梁模式关注的是抽象和实现的分离,它是结构型模式,结构型模式研究的是如何建立一个软件架构,那我们就来看看桥梁模式是如何构件一套发送邮件的架构,如图33-3所示。
![]()
图33-3 桥梁模式发送邮件类图
类图中我们增加了SendMail和Postfix两个邮件服务器实现类,在邮件模版中允许增加发送者标记,其他与策略模式都相同。确实很相似,大家看看,我们在这里已经完成了一个独立的架构,邮件有了,发送邮件的服务器也具备了,是一个完整的邮件发送程序。需要读者注意的是,SendMail类不是一个动词行为(发送邮件),它指的是一款开源邮件服务器产品,一般*nix系统的默认邮件服务器就是SendMail;Postfix也是一款开源的邮件服务器产品,其性能、稳定性都在逐步超越SendMail。
我们来看代码实现,邮件模板仅仅增加了一个add方法,如代码清单33-7所示。
代码清单33-6 邮件模版
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public abstract class MailTemplate {/**该部分代码不变,请参考代码清单33-1所示。*///允许增加邮件发送标志public void add(String sendInfo){context = sendInfo + context;}} 文本邮件、超文本邮件都没有任何改变,如代码清单33-2、33-3所示,这里就不再赘述。 我们来看邮件服务器,也就是桥梁模式的抽象化角色,如代码清单33-7所示。代码清单33-7 邮件服务器public abstract class MailServer {//发送的是哪封邮件protected final MailTemplate m;public MailServer(MailTemplate _m){this.m = _m;}//发送邮件public void sendMail(){System.out.println("====正在发送的邮件信息====");//发件人System.out.println("发件人:" + m.getFrom());//收件人System.out.println("收件人:" + m.getTo());//标题System.out.println("邮件标题:" + m.getSubject() );//邮件内容System.out.println("邮件内容:" + m.getContext());}} |
该类相对于策略模式的环境角色有两个改变:
◇ 修改为抽象类
为什么要修改成为抽象类?因为我们在设计一个架构,想想看邮件服务器是一个具体的、可实例化的对象吗?“给我一台邮件服务器”能实现吗?不能,只能说“给我一台Postfix邮件服务”,这才能实现,必须有一个明确的、可指向对象。
◇ 变量m修改为Protected访问权限,方便子类调用
那我们再来看看Postfix邮件服务器的实现,如代码清单33-8所示。
代码清单33-8 Postfix邮件服务器
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class Postfix extends MailServer {public Postfix(MailTemplate _m) {super(_m);}//修正邮件发送程序public void sendMail(){//增加邮件服务器信息String context ="Received: from XXXX (unknown [xxx.xxx.xxx.xxx]) by aaa.aaa.com (Postfix) with ESMTP id 8DBCD172B8\n" ;super.m.add(context);super.sendMail();}} |
为什么要覆写sendMail程序呢?是因为每个邮件服务器在发送邮件时都会在邮件内容上留下自己的标志,一是广告作用,二是方便互联网上统计需要,三是方便同质软件的共振。我们再来看SendMail邮件服务的实现,如代码清单33-9所示。
代码清单33-9 SendMail邮件服务器
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class SendMail extends MailServer {//传递一份邮件public SendMail(MailTemplate _m) {super(_m);}//修正邮件发送程序@Overridepublic void sendMail(){//增加邮件服务器信息super.m.add("Received: (sendmail); 7 Nov 2009 04:14:44 +0100");super.sendMail();}} |
邮件和邮件服务器都有了,我们来看怎么发送邮件,如代码清单33-10所示。
代码清单33-10 场景类
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class Client {public static void main(String[] args) {//创建一封TEXT格式的邮件MailTemplate m = new HtmlMail("a@a.com","b@b.com","外星人攻击地球了","结局是外星人被中国人熬汤炖着吃了!");//使用postfix发送邮件MailServer mail = new Postfix(m);//发送邮件mail.sendMail();}} |
运行结果如下所示:
====正在发送的邮件信息====
发件人:a@a.com
收件人:b@b.com
邮件标题:外星人攻击地球了
邮件内容:
Content-Type: multipart/mixed;charset=GB2312
Received: from XXXX (unknown [xxx.xxx.xxx.xxx]) by aaa.aaa.com (Postfix) with ESMTP id 8DBCD172B8
结局是外星人被中国人熬汤炖着吃了!
邮件格式为:超文本格式
当然了,还有其他三种发送邮件的方式:Postfix发送文本邮件,SendMail发送文本邮件和超文本邮件,修改很小,读者可以自行修改实现,体现一下桥梁模式的不同点。
33.1.3 最佳实践
策略模式和桥梁模式是如此相似,我们只能从它们的意图上来分析,策略模式是一个行为模式,旨在封装一系列的行为,在例子中我们认为把邮件的必要信息(发件人、收件人、标题、内容)封装成一个对象就是一个行为,封装的格式(算法)不同,行为也就不同。而桥梁模式则是解决在不破坏封装的情况下如何抽取出它的抽象部分和实现部分,它的前提是不破坏封装,让抽象部分和实现部分都可以独立地变化,在例子中,我们的邮件服务器和邮件模版是不是都可以独立地变化?不管是邮件服务器还是邮件模板,只要继承了抽象类就可以继续扩展,它的主旨是建立一个不破坏封装性的可扩展架构。
精简地来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。
还是很难区分,是吧?多想想他们两者的意图,就可以理解为什么要建立两个相似的模式了。不要太多考虑它们之间的区别了,使用才是正道,我们在做系统设计时,可以不考虑到底使用的是策略模式还是桥梁模式,只要好用,能够解决问题就成,“不管黑猫白猫,抓住老鼠的就是好猫”。
策略模式 VS 桥梁模式的更多相关文章
- 【设计模式】 模式PK:策略模式VS桥梁模式
1.概述 我们先来看两种模式的通用类图. 两者之间确实很相似.如果把策略模式的环境角色变更为一个抽象类加一个实现类,或者桥梁模式的抽象角色未实现,只有修正抽象化角色,想想看,这两个类图有什么地方不一样 ...
- Java设计模式(13)——结构型模式之桥梁模式(Bridge)
一.概述 概念 将抽象与实现脱耦,使得抽象和实现可以独立运行 UML图 角色: 角色关系 二.实践 按照上面的角色建立相应的类 抽象化角色 /** * 抽象化角色 * * @author Admini ...
- java设计模式之桥梁模式(Bridge)
1.桥梁模式 与 策略模式 非常相似 (其实很多设计模式都相似,因为所有的模式都是按照设计原则 而设计出来的,设计原则就相当于武功的心法,设计模式就是招式,只要心法过硬,就可以无招胜有招了.) 这里也 ...
- JAVA设计模式之桥梁模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...
- 桥梁模式(Bridge Pattern)
桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation) ...
- 《JAVA与模式》之桥梁模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...
- java 设计模式 之 桥梁模式
桥梁模式:将抽象和实现解耦,使两者可以独立的变化.解释:将两个有组合关系,强耦合的对象,各自抽象然后解耦.(类关系图看https://www.cnblogs.com/blogxiao/p/951388 ...
- 设计模式 桥梁模式 JDBC
桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是“将抽象化(Abstraction)与实现化(Implementation) ...
- Java 策略模式和状态模式
本文是转载的,转载地址:大白话解释Strategy模式和State模式的区别 先上图: 本质上讲,策略模式和状态模式做得是同一件事:去耦合.怎么去耦合?就是把干什么(语境类)和怎么干(策略接口)分开, ...
随机推荐
- crawlspider抽屉爬取实例+分布
创建项目 scrapy startproject choutiPro 创建爬虫文件 scrapy genspider -t crawl chouti www.xxx.com 进入pycharm 培训 ...
- caioj 1066 动态规划入门(一维一边推4:护卫队)(分组型dp总结)
很容易想到f[i]为前i项的最优价值,但是我一直在纠结如果重量满了该怎么办. 正解有点枚举的味道. 就是枚举当前这辆车与这辆车以前的组合一组,在能组的里面取最优的. 然后要记得初始化,因为有min,所 ...
- sendfile复习
之前有一篇文章: http://www.cnblogs.com/charlesblc/p/6341605.html 今天又看到其他的一篇: http://www.cnblogs.com/fengyv/ ...
- 配置Xcode版本控制SVN详细步骤内含解决Xcode/Mac OS10.8无法配置SVN的解决方法
本站文章均为李华明Himi原创,转载务必在明显处注明:(作者新浪微博:@李华明Himi ) 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/game-de ...
- python-安装xlrd xlwt 插件
最近需要对比两个表格的内容,然后修改其中的某列内容.因为工作量太大,所以想通过python来实现.上网查了相关的操作,其中牵扯到两个功能模块,xlrd xlwt.这两个功能模块分别是对excel进行读 ...
- Spark RPC
在Spark中,对于网络调用的底层封装(粘包拆包,编解码,链路管理等)都是在common/network-common包中实现的(详见[common/network-common]).在common/ ...
- deep-in-es6(二)
es6-生成器Generators: eg: function* quips(name) { yield "您好"+name+"!"; if(name.star ...
- read---读取变量值
read命令从键盘读取变量的值,通常用在shell脚本中与用户进行交互的场合.该命令可以一次读取多个变量的值,变量和输入的值都需要使用空格隔开.在read命令后面,如果没有指定变量名,读取的数据将被自 ...
- pstree---树状图的方式展现进程
pstree命令以树状图的方式展现进程之间的派生关系,显示效果比较直观. 语法 pstree(选项) 选项 -a:显示每个程序的完整指令,包含路径,参数或是常驻服务的标示: -c:不使用精简标示法: ...
- Knockout 重新绑定注意要点
function ReImport(id) { //点击按钮时调用函数名称, var node = document.getElementById('bindingNode'); //bindingN ...