`

策略模式 VS 桥梁模式

 
阅读更多

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

 

clip_image002

图33-1 策略模式(左)和桥梁模式(右)通用类图

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

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

33.1.1 策略模式发送邮件

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

clip_image004

图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/plain
 
String 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/mixed
 
String 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所示。

clip_image006

图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-233-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);
 
}
 
//修正邮件发送程序
 
@Override
 
public 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 最佳实践

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

      精简地来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。

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

 

 

原作者:http://www.cnblogs.com/cbf4life/

分享到:
评论

相关推荐

    java设计模式

    32.1 命令模式VS策略模式 32.1.1 策略模式实现压缩算法 32.1.2 命令模式实现压缩算法 32.1.3 小结 32.2 策略模式VS状态模式 32.2.1 策略模式实现人生 32.2.2 状态模式实现人生 32.2.3 小结 32.3 观察者模式VS责任链...

    Java24种设计模式,Java24种设计模式,24种设计模式,学会了这24种设计模式,可以打遍天下无敌手,设计模式非常重要

    1、策略模式STRATEGY PATTERN 2、代理模式PROXY PATTERN 3、单例模式SINGLETON PATTERN 4、多例模式MULTITION PATTERN 5、工厂方法模式FACTORY METHOD PATTERN 6、抽象工厂模式ABSTRACT FACTORY PATTERN 7、门面模式...

    基于设计模式的画图程序

    至少在其中运用 3 种模式,其中涉及到的模式有装饰模式、策略模式、桥梁模式三种。 1.2 画图基本要求 能实现基本图形的绘制功能 1.3 画图高级要求 实现图形的操作(如选取、移动、放大、缩小、改变颜色、改变线形等...

    深入浅出java设计模式(高清中文PDF)

    桥梁模式 7.组合模式 8.装饰模式 9.门面模式 10.享元模式 11.代理模式 12.责任链模式 13.命令模式 14.解释器模式 15.迭代器模式 16.调停者模式 17.备忘录模式 18.观察者模式 19.策略模式 20.状态模式 ...

    基于设计模式的绘图程序

    至少在其中运用 3 种模式,其中涉及到的模式有装饰模式、策略模式、桥梁模式三种。 1.2 画图基本要求 能实现基本图形的绘制功能 1.3 画图高级要求 实现图形的操作(如选取、移动、放大、缩小、改变颜色、改变线形等...

    C#设计模式.PDF

    六、 在什么情况下应当使用桥梁模式 158 设计模式(17)-Chain of Responsibility Pattern 158 一、 职责链(Chain of Responsibility)模式 160 二、 责任链模式的结构 160 三、 责任链模式的示意性源代码 160 四...

    设计模式课程设计- 画 图 程 序.doc

    至少在其中运用 6 种模式,其中涉及到的模式有装饰模式、策略模式、桥梁模式三种。 1.2 画图基本要求 能实现基本图形的绘制功能 1.3 画图高级要求 实现图形的操作(如选取、移动、放大、缩小、改变颜色、改变线形等...

    C#设计模式大全

    六、 在什么情况下应当使用桥梁模式 设计模式(17)-Chain of Responsibility Pattern 一、 职责链(Chain of Responsibility)模式 二、 责任链模式的结构 三、 责任链模式的示意性源代码 四、 纯的与不纯的...

    java设计模式资源下载

    策略模式、代理模式、单例模式、多例模式、工厂方法模式、抽象工厂模式、观察这模式、适配器模式、门面模式、桥梁模式

    深入浅出设计模式

    深入浅出设计模式,比head first更...桥梁模式 组合模式 装饰模式 门面模式 享元模式 代理模式 责任链模式 命令模式 解析器模式 迭代器模式 调停者模式 备忘录模式 观察者模式 策略模式 状态模式 模板模式 访问者模式

    JAVA设计模式.rar

    策略模式【STRATEGY PATTERN】 代理模式【PROXY PATTERN】 单例模式【SINGLETON PATTERN】  多例模式【MULTITION PATTERN】  工厂方法模式【FACTORY METHOD PATTERN】 抽象工厂模式【ABSTRACT FACTORY...

    java和设计模式ppt教程

    java和设计模式ppt包含工厂模式、建造模式、原始模型模式、单例模式、结构模式、适配器、桥梁模式、合成模式、装饰模式、门面模式、享元模式、代理模式、行为模式、解释器模式、迭代子模式、调停者模式、备忘录模式...

    C#23种设计模式_示例源代码及PDF

    桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关 桥梁模式 联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是 继承关系,从而使两者可以独立的...

    java 23种设计模式.7z

    java 设计模式 单例设计模式 工厂设计模式 装饰设计模式 策略模式 代理模式 多例模式 抽象工厂模式 门面模式 适配器模式 模板方法模式 建造者模式 桥梁模式...

    源码:阎宏设计模式光盘

    com.javapatterns.bridge 桥梁模式 com.javapatterns.builder 建造者模式 com.javapatterns.carp 组合/聚合复用原则 com.javapatterns.chainofresp 责任链模式 com.javapatterns.chainofresp.scheduler 专题:...

    [源代码] 修炼Java开发技术 在架构中体验设计模式和算法之美 (源代码)

    - (Chapter03.rar)Chapter 04 单例模式 - (Chapter04.rar)Chapter 05 建造者模式 - (Chapter05.rar)Chapter 06 原型模式 - (Chapter06.rar)Chapter 07 适配器模式 - (Chapter07.rar)Chapter 08 桥梁模式 - (Chapter...

    24个设计模式与6大设计原则

    第 1 章 策略模式【STRATEGY PATTERN】 4 第 2 章 代理模式【PROXY PATTERN】 8 第 3 章 单例模式【SINGLETON PATTERN】 12 第 4 章 多例模式【MULTITION PATTERN】 16 第 5 章 工厂方法...

    java26个设计模式

    工厂模式Factory 原始Prototype 单例Singleton 建造Builder 多例Multiton 适配器Adepter 装饰Decorator 合成Composite 代理Proxy 享元Flyweight 门面Facade 桥梁Bridge 不变Immutable 策略Strategy 模版Template ...

    复习提纲之设计模式总结1

    1.创建模式:工厂方法、简单工厂、抽象工厂、单例、建造、模型 2.结构模式:适配器、缺省适配、合成、装饰、代理、享元、门面、桥梁 3.行为模式:不变、策略、模板

Global site tag (gtag.js) - Google Analytics