这对冤家终于碰头了,策略模式与桥梁模式是如此相似,简直就是孪生兄弟,要把它们两个分开需要花费大量智力,我们来看看两者的通用类图,如图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/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所示。
图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);
} //修正邮件发送程序 @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 最佳实践
策略模式和桥梁模式是如此相似,我们只能从它们的意图上来分析,策略模式是一个行为模式,旨在封装一系列的行为,在例子中我们认为把邮件的必要信息(发件人、收件人、标题、内容)封装成一个对象就是一个行为,封装的格式(算法)不同,行为也就不同。而桥梁模式则是解决在不破坏封装的情况下如何抽取出它的抽象部分和实现部分,它的前提是不破坏封装,让抽象部分和实现部分都可以独立地变化,在例子中,我们的邮件服务器和邮件模版是不是都可以独立地变化?不管是邮件服务器还是邮件模板,只要继承了抽象类就可以继续扩展,它的主旨是建立一个不破坏封装性的可扩展架构。
精简地来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。
还是很难区分,是吧?多想想他们两者的意图,就可以理解为什么要建立两个相似的模式了。不要太多考虑它们之间的区别了,使用才是正道,我们在做系统设计时,可以不考虑到底使用的是策略模式还是桥梁模式,只要好用,能够解决问题就成,“不管黑猫白猫,抓住老鼠的就是好猫”。
相关推荐
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责任链...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件开发中,我们经常遇到需要根据不同的条件或场景来执行不同算法的情况。策略模式提供了一种将算法族封装到各自独立的类中,并让它们之间可以互相...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件开发中,我们经常遇到需要根据不同的条件或场景来选择不同算法或策略的情况。策略模式提供了一种灵活的方式来处理这种变化,使得代码更加模块化...
在实际应用中,桥梁模式常用于需要为同一抽象类提供多种实现的场景,例如图形界面库中,图形对象(如按钮、文本框)与绘制策略(如Windows绘图、Mac绘图)的分离,或者游戏引擎中角色动作和动画效果的分离等。...
1、策略模式STRATEGY PATTERN 2、代理模式PROXY PATTERN 3、单例模式SINGLETON PATTERN 4、多例模式MULTITION PATTERN 5、工厂方法模式FACTORY METHOD PATTERN 6、抽象工厂模式ABSTRACT FACTORY PATTERN 7、门面模式...
【Java设计模式】《设计模式之禅》中的23种设计模式.zip 【Java设计模式】《设计模式之禅》中的23种设计模式.zip 【Java设计模式】《设计模式之禅》中的23种设计模式.zip 【Java设计模式】《设计模式之...23. 桥梁模式
本书《您的设计模式》对设计模式进行了详细的讲解,涵盖了策略模式、代理模式、单例模式、多例模式、工厂方法模式、抽象工厂模式、门面模式、适配器模式、模板方法模式、建造者模式、桥梁模式、命令模式、装饰模式、...
至少在其中运用 3 种模式,其中涉及到的模式有装饰模式、策略模式、桥梁模式三种。 1.2 画图基本要求 能实现基本图形的绘制功能 1.3 画图高级要求 实现图形的操作(如选取、移动、放大、缩小、改变颜色、改变线形等...
桥梁模式可以使系统更加清晰和灵活,例如在画图程序中可以使用桥梁模式来将图形的绘制和显示解耦。 3.2 策略模式 策略模式是一种行为型模式,用于定义一系列算法,例如图形的绘制和显示。策略模式可以使系统更加...
1. 创建型模式包括:单例模式、多例模式、工厂方法、抽象工厂模式、门面模式、适配器模式、模板方法模式、建造者模式、桥梁模式和原型模式。 2. 结构型模式包括:策略模式、代理模式、适配器模式、桥接模式、组合...
9. 策略模式(Strategy):策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。 10. 模板方法模式(Template Method):模板方法模式在一个抽象类...
至少在其中运用 3 种模式,其中涉及到的模式有装饰模式、策略模式、桥梁模式三种。 1.2 画图基本要求 能实现基本图形的绘制功能 1.3 画图高级要求 实现图形的操作(如选取、移动、放大、缩小、改变颜色、改变线形等...
适配器模式(Adapter)允许不同接口的对象协同工作,桥梁模式(Bridge)将抽象部分与其实现部分分离,使它们可以独立变化。装饰模式(Decorator)可以在运行时动态地给对象添加新的行为或责任,而代理模式(Proxy)...
基于策略模式的中医数据挖掘平台是在现代计算机科学和传统中医之间搭建的一座桥梁,通过引入面向对象编程的设计模式之一——策略模式,来指导中医数据挖掘科研软件平台的设计和开发。这一平台的提出和实现,不仅推动...
所涉及的设计模式包括工厂模式、单例模式、建造模式、原型模式、适配器模式、桥梁模式、组合模式、装饰模式、门面模式、享元模式、代理模式、责任链模式、命令模式、解释器模式、迭代器模式、调停者模式、备忘录模式...
Model负责管理应用程序的数据和业务逻辑,View负责显示数据,而Controller作为两者之间的桥梁,处理用户输入并更新Model,进而刷新View。 2. **单例模式**:它保证一个类只有一个实例,并提供一个全局访问点。在...
代理模式(Proxy Pattern)、单例模式(Singleton Pattern)、工厂方法模式...桥梁模式(Bridge Pattern)、命令模式(Command Pattern)、装饰模式(Decorator Pattern)、迭代器模式(Iterator Pattern)、组合模式...
例如,工厂模式可用于创建BLL和DAL的实例,策略模式可以用来实现不同的业务逻辑策略,而门面模式可以作为BLL和UI之间的桥梁,简化交互过程。 总的来说,这份“设计模式三层结构C#源码”资源为开发者提供了一个学习...
本文将深入探讨几种在iOS开发中常用的设计模式:代理模式、观察者模式、MVC模式、单例模式、策略模式以及工厂模式。 1. **代理模式**: 代理模式在iOS开发中广泛应用,主要用于对象间通信。例如,UITableView的...