33.3 包装模式群PK
我们讲了这么多的设计模式,大家有没有发觉在很多的模式中有些角色是不干活的?它们只是充当黔首作用,你有问题,找我,但我不处理,我让其他人处理。最典型的就是代理模式了,代理角色接收请求然后传递到被代理角色处理。门面模式也是一样,门面角色的任务就是把请求转发到子系统。类似这种结构的模式还有很多,我们先给这种类型的模式定义一个名字,叫做包装模式(wrapping pattern)。注意,包装模式是一组模式而不是一个。包装模式包括哪些设计模式呢?包装模式包括:装饰模式、适配器模式、门面模式、代理模式、桥梁模式。下面我们通过一组例子来说明这五个包装模式的区别。
33.3.1 代理模式
现在很多明星都有经纪人,一般有什么事他们都会说:“你找我的经纪人谈好了”,下面我们就看看这一过程怎么模拟。假设有一个追星族想找明星签字,我们看看采用代理模式怎么实现。代理模式是包装模式中的最一般的实现,类图如图33-6所示。
![](https://box.kancloud.cn/2016-08-14_57b0036f3f06b.jpg)
图33-6 追星族找明星签字
类图很简单,就是一个简单的代理模式,我们来看明星的定义,明星接口如代码清单33-29所示。
代码清单33-29 明星接口
public interface IStar {
//明星都会签名
public void sign();
}
明星只有一个行为:签字。我们来看明星的实现,如代码清单33-30所示。
代码清单33-30 明星
public class Singer implements IStar {
public void sign() {
System.out.println("明星签字:我是XXX大明星");
}
}
经纪人与明星应该有相同的行为,比如说签名,虽然经纪人不签名,但是他把你要签名的笔记本、衣服、CD等传递过去让真正的明星签字,经纪人如代码清单33-31所示。
代码清单33-31 经纪人
public class Agent implements IStar {
//定义是谁的经纪人
private IStar star;
//构造函数传递明星
public Agent(IStar _star){
this.star = _star;
}
//经纪人是不会签字的,签字了歌迷也不认
public void sign() {
star.sign();
}
}
应该非常明确地指出一个经纪人是谁的代理,因此要在构造函数中接收一个明星对象,确定是要做这个明星的代理。我们再来看看追星族是怎么找明星签字的,如代码清单33-32所示。
代码清单33-32 追星族
public class Idolater {
public static void main(String[] args) {
//崇拜的明星是谁
IStar star = new Singer();
//找到明星的经纪人
IStar agent = new Agent(star);
System.out.println("追星族:我是你的崇拜者,请签名!");
//签字
agent.sign();
}
}
很简单,找到明星的代理,然后明星就签字了。运行结果如下所示:
追星族:我是你的崇拜者,请签名!
明星签字:我是XXX大明星
看看我们的程序逻辑,我们是找明星的经纪人签字,真实签字的是明星,经纪人只是把这个请求传递给明星处理而已,这是普通的代理模式的典型应用。
33.3.2 装饰模式
明星也都是一步一步地奋斗出来的,谁都不是一步就成为大明星的。甚至一些演员通过粉饰自己给观众一个好的印象,现在我们就来看怎么粉饰一个演员,如图33-7所示。
![](https://box.kancloud.cn/2016-08-14_57b0036f52f3e.jpg)
图33-7 演技修饰
下面我们就来看看这些过程如何实现,先看明星接口,如代码清单33-33所示。
代码清单33-33 明星接口
public interface IStar {
//演戏
public void act();
}
我们来看看我们的主角,如代码清单33-34所示。
代码清单33-34 假明星
public class FreakStar implements IStar {
public void act() {
System.out.println("演中:演技很拙劣");
}
}
我们看看这个明星是怎么粉饰的,先定义一个抽象装饰类,如代码清单33-35所示。
代码清单33-35 抽象装饰类
public abstract class Decorator implements IStar {
//粉饰的是谁
private IStar star;
public Decorator(IStar _star){
this.star = _star;
}
public void act() {
this.star.act();
}
}
前后两次修饰,开演前毫无忌惮地吹嘘,如代码清单33-36所示。
代码清单33-36 吹大话
public class HotAir extends Decorator {
public HotAir(IStar _star){
super(_star);
}
public void act(){
System.out.println("演前:夸夸其谈,没有自己不能演的角色");
super.act();
}
}
大家发现这个明星演技不好的时候,他拼命找借口,说是那天天气不好、心情不好等,如代码清单33-37所示。
代码清单33-37 抵赖
public class Deny extends Decorator {
public Deny(IStar _star){
super(_star);
}
public void act(){
super.act();
System.out.println("演后:百般抵赖,死不承认");
}
}
我们建立一个场景把这种情况展示一下,如代码清单33-38所示。
代码清单33-38 场景类
public class Client {
public static void main(String[] args) {
//定义出所谓的明星
IStar freakStar = new FreakStar();
//看看他是怎么粉饰自己的
//演前吹嘘自己无所不能
freakStar = new HotAir(freakStar);
//演完后,死不承认自己演的不好
freakStar = new Deny(freakStar);
System.out.println("====看看一些虚假明星的形象====");
freakStar.act();
}
}
运行结果如下所示:
====看看一些虚假明星的形象====
演前:夸夸其谈,没有自己不能演的角色
演中:演技很拙劣
演后:百般抵赖,死不承认
33.3.3 适配器模式
我们知道在演艺圈中还存在一种情况:替身,替身也是演员,只是普通的演员而已,在一段戏中,前十五分钟是明星本人,后十五分钟也是明星本人,就中间的五分钟是替身,那这个场景该怎么描述呢?注意中间那五分钟,这个时候一个普通演员被导演认为是明星演员,我们来看类图,如图33-8所示。
![](https://box.kancloud.cn/2016-08-14_57b0036f6adc7.jpg)
图33-8 替身演员类图
导演找了一个普通演员作为明星的替身,不过观众看到的还是明星的身份。我们来看代码,首先看明星接口,如代码清单33-39所示。
代码清单33-39 明星接口
public interface IStar {
//明星都要演戏
public void act(String context);
}
再来看一个具体的电影明星,他的主要职责就是演戏,如代码清单33-40所示。
代码清单33-40 电影明星
public class FilmStar implements IStar {
public void act(String context) {
System.out.println("明星演戏:" + context);
}
}
我们再来看普通演员,明星就那么多,但是普通演员非常多,我们看其接口,如代码清单33-41所示。
代码清单33-41 普通演员接口
public interface IActor {
//普通演员演戏
public void playact(String contet);
}
普通演员也是演员,是要演戏的,我们来看一个普通演员的实现,如代码清单33-42所示。
代码清单33-42 普通演员
public class UnknownActor implements IActor {
//普通演员演戏
public void playact(String context) {
System.out.println("普通演员:"+context);
}
}
我们来看替身该怎么编写,如代码清单33-43所示。
代码清单33-43 替身演员
public class Standin implements IStar {
private IActor actor;
//替身是谁
public Standin(IActor _actor){
this.actor = _actor;
}
public void act(String context) {
actor.playact(context);
}
}
这是一个通用的替身,哪个普通演员能担任哪个明星的替身是由导演决定的,导演想让谁当就让谁当,我们来看导演,如代码清单33-44所示。
代码清单33-44 导演类
public class direcotr {
public static void main(String[] args) {
System.out.println("=======演戏过程模拟==========");
//定义一个大明星
IStar star = new FilmStar();
star.act("前十五分钟,明星本人演戏");
//导演把一个普通演员当做明星演员来用
IActor actor = new UnknownActor();
IStar standin= new Standin(actor);
standin.act("中间五分钟,替身在演戏");
star.act("后十五分钟,明星本人演戏");
}
}
运行结果如下所示:
=======演戏过程模拟==========
明星演戏:前十五分钟,明星本人演戏
普通演员:中间五分钟,替身在演戏
明星演戏:后十五分钟,明星本人演戏
这里使用了适配器模式,把一个普通的演员转换为一个明星演员。
33.3.4 桥梁模式
我们继续说明星圈的事情,现在明星类型太多了,比如电影明星、电视明星、歌星、体育明星、网络明星等,每个类型的明星都有明确的职责,电影明星的主要工作就是演电影,电视明星的主要工作就是演电视剧或者主持电视节目。再看看现在的明星,单一发展的基本没有,主持人出专辑、体育明星演电影、歌星拍戏等太平常了,我们就用程序来表现一下多元化情形,如图33-9所示。
![](https://box.kancloud.cn/2016-08-14_57b0036f7ffbb.jpg)
图33-9 各类明星描述
图33-9中定义了一个抽象明星AbsStar,然后产生出各个具体类型的明星,比如电影明星FilmStar、歌星Singer,当然还可以继续扩展下去。这里还定义了一个抽象的行为AbsAction,描述明星所具有的活动,比如演电影、唱歌等,在这种设计下,明星可以扩展,明星的活动也可以扩展,非常灵活。我们先来看明星的活动,抽象活动如代码清单33-45所示。
代码清单33-45 抽象活动
public abstract class AbsAction {
//每个活动都有描述
public abstract void desc();
}
很简单,只有一个活动的描述,由子类来实现。我们来看演电影和唱歌两个活动,分别如代码清单33-46、33-47所示。
代码清单33-46 演电影
public class ActFilm extends AbsAction {
public void desc() {
System.out.println("演出精彩绝伦的电影");
}
}
代码清单33-47 唱歌
public class Sing extends AbsAction {
public void desc() {
System.out.println("唱出优美的歌曲");
}
}
各种精彩的活动都有了,我们再来看抽象明星,它是所有明星的代表,如代码清单33-48所示。
代码清单33-48 抽象明星
public abstract class AbsStar {
//一个明星参加哪些活动
protected final AbsAction action;
//通过构造函数传递具体活动
public AbsStar(AbstAction _action){
this.action = _action;
}
//每个明星都有自己的主要工作
public void doJob(){
action.desc();
}
}
明星都有自己的主要活动(或者是主要工作),我们在抽象明星中只是定义明星有活动,具体有什么活动由各个子类实现。我们再来看电影明星,如代码清单33-49所示。
代码清单33-49 电影明星
public class FilmStar extends AbsStar {
//默认的电影明星的主要工作是拍电影
public FilmStar(){
super(new ActFilm());
}
//也可以重新设置一个新职业
public FilmStar(AbsAction _action){
super(_action);
}
//细化电影明星的职责
public void doJob(){
System.out.println("\n======影星的工作=====");
super.doJob();
}
}
电影明星的本职工作就应该是演电影,因此就有了一个无参构造函数来定义电影明星的默认工作,如果明星要客串一下去唱歌也可以,有参构造解决了该问题。歌星的实现与此相同,如代码清单33-50所示。
代码清单33-50 歌星
public class Singer extends AbsStar {
//歌星的默认活动是唱歌
public Singer(){
super(new Sing());
}
//也可以重新设置一个新职业
public Singer(AbsAction _action){
super(_action);
}
//细化歌星的职责
public void doJob(){
System.out.println("\n======歌星的工作=====");
super.doJob();
}
}
我们使用电影明星和歌星来作为代表,这两类明星也是我们经常听到或看到的,下面建立一个场景类来模拟一下明星的事迹,如代码清单33-51所示。
代码清单33-51 场景类
public class Client {
public static void main(String[] args) {
//声明一个电影明星
AbsStar zhangSan = new FilmStar();
//声明一个歌星
AbsStar liSi = new Singer();
//展示一下各个明星的主要工作
zhangSan.doJob();
liSi.doJob();
//当然,也有部分明星不务正业,比如歌星演戏
liSi = new Singer(new ActFilm());
liSi.doJob();
}
}
运行结果如下所示:
======影星的工作=====
演出精彩绝伦的电影
======歌星的工作=====
唱出优美的歌曲
======歌星的工作=====
演出精彩绝伦的电影
好了,各类明星都有自己的本职工作,但是偶尔客串一个其他类型的活动也是允许的,如此设计后,明星就可以不用固定在自己的本职工作上,而是向其他方向发展,比如影视歌三栖明星。
门面模式我们在其他章节已经讲解得比较多了,本小节就不再赘述。
33.3.5 最佳实践
5个包装模式是大家在系统设计中经常会用到的模式,它们具有相似的特征:都是通过委托的方式对一个对象或一系列对象(例如门面模式)施行包装,有了包装,设计的系统才更加灵活、稳定,并且极具扩展性。从实现的角度来看,它们都是代理的一种具体表现形式,我们来看看它们在使用场景上有什么区别。
代理模式主要用在不希望展示一个对象内部细节的场景中,比如一个远程服务不需要把远程连接的所有细节都暴露给外部模块,通过增加一个代理类,可以很轻松地实现被代理类的功能封装。此外,代理模式还可以用在一个对象的访问需要限制的场景中,比如AOP。
装饰模式是一种特殊的代理模式,它倡导的是在不改变接口的前提下为对象增强功能,或者动态添加额外职责。就扩展性而言,它比子类更加灵活,例如在一个已经运行的项目中,可以很轻松地通过增加装饰类来扩展系统的功能。
适配器模式的主要意图是接口转换,把一个对象的接口转换成系统希望的另外一个接口,这里的系统指的不仅仅是一个应用,也可能是某个环境,比如通过接口转换可以屏蔽外界接口,以免外界接口深入系统内部,从而提高系统的稳定性和可靠性。
桥梁模式是在抽象层产生耦合,解决的是自行扩展的问题,它可以使两个有耦合关系的对象互不影响地扩展,比如对于使用笔画图这样的需求,可以采用桥梁模式设计成用什么笔(铅笔、毛笔)画什么图(圆形、方形)的方案,至于以后需求的变更,如增加笔的类型,增加图形等,对该设计来说是小菜一碟。
门面模式是一个粗粒度的封装,它提供一个方便访问子系统的接口,不具有任何的业务逻辑,仅仅是一个访问复杂系统的快速通道,没有它,子系统照样运行,有了它,只是更方便访问而已。
- 前言
- 第一部分 大旗不挥,谁敢冲锋——6大设计原则全新解读
- 第1章 单一职责原则
- 1.2 绝杀技,打破你的传统思维
- 1.3 我单纯,所以我快乐
- 1.4 最佳实践
- 第2章 里氏替换原则
- 2.2 纠纷不断,规则压制
- 2.3 最佳实践
- 第3章 依赖倒置原则
- 3.2 言而无信,你太需要契约
- 3.3 依赖的三种写法
- 3.4 最佳实践
- 第4章 接口隔离原则
- 4.2 美女何其多,观点各不同
- 4.3 保证接口的纯洁性
- 4.4 最佳实践
- 第5章 迪米特法则
- 5.2 我的知识你知道得越少越好
- 5.3 最佳实践
- 第6章 开闭原则
- 6.2 开闭原则的庐山真面目
- 6.3 为什么要采用开闭原则
- 6.4 如何使用开闭原则
- 6.5 最佳实践
- 第二部分 真刀实枪 ——23种设计模式完美演绎
- 第7章 单例模式
- 7.2 单例模式的定义
- 7.3 单例模式的应用
- 7.4 单例模式的扩展
- 7.5 最佳实践
- 第8章 工厂方法模式
- 8.2 工厂方法模式的定义
- 8.3 工厂方法模式的应用
- 8.4 工厂方法模式的扩展
- 8.5 最佳实践
- 第9章 抽象工厂模式
- 9.2 抽象工厂模式的定义
- 9.3 抽象工厂模式的应用
- 9.4 最佳实践
- 第10章 模板方法模式
- 10.2 模板方法模式的定义
- 10.3 模板方法模式的应用
- 10.4 模板方法模式的扩展
- 10.5 最佳实践
- 第11章 建造者模式
- 11.2 建造者模式的定义
- 11.3 建造者模式的应用
- 11.4 建造者模式的扩展
- 11.5 最佳实践
- 第12章 代理模式
- 12.2 代理模式的定义
- 12.3 代理模式的应用
- 12.4 代理模式的扩展
- 12.5 最佳实践
- 第13章 原型模式
- 13.2 原型模式的定义
- 13.3 原型模式的应用
- 13.4 原型模式的注意事项
- 13.5 最佳实践
- 第14章 中介者模式
- 14.2 中介者模式的定义
- 14.3 中介者模式的应用
- 14.4 中介者模式的实际应用
- 14.5 最佳实践
- 第15章 命令模式
- 15.2 命令模式的定义
- 15.3 命令模式的应用
- 15.4 命令模式的扩展
- 15.5 最佳实践
- 第16章 责任链模式
- 16.2 责任链模式的定义
- 16.3 责任链模式的应用
- 16.4 最佳实践
- 第17章 装饰模式
- 17.2 装饰模式的定义
- 17.3 装饰模式应用
- 17.4 最佳实践
- 第18章 策略模式
- 18.2 策略模式的定义
- 18.3 策略模式的应用
- 18.4 策略模式的扩展
- 18.5 最佳实践
- 第19章 适配器模式
- 19.2 适配器模式的定义
- 19.3 适配器模式的应用
- 19.4 适配器模式的扩展
- 19.5 最佳实践
- 第20章 迭代器模式
- 20.2 迭代器模式的定义
- 20.3 迭代器模式的应用
- 20.4 最佳实践
- 第21章 组合模式
- 21.2 组合模式的定义
- 21.3 组合模式的应用
- 21.4 组合模式的扩展
- 21.5 最佳实践
- 第22章 观察者模式
- 22.2 观察者模式的定义
- 22.3 观察者模式的应用
- 22.4 观察者模式的扩展
- 22.5 最佳实践
- 第23章 门面模式
- 23.2 门面模式的定义
- 23.3 门面模式的应用
- 23.4 门面模式的注意事项
- 23.5 最佳实践
- 第24章 备忘录模式
- 24.2 备忘录模式的定义
- 24.3 备忘录模式的应用
- 24.4 备忘录模式的扩展
- 24.5 最佳实践
- 第25章 访问者模式
- 25.2 访问者模式的定义
- 25.3 访问者模式的应用
- 25.4 访问者模式的扩展
- 25.5 最佳实践
- 第26章 状态模式
- 26.2 状态模式的定义
- 26.3 状态模式的应用
- 第27章 解释器模式
- 27.2 解释器模式的定义
- 27.3 解释器模式的应用
- 27.4 最佳实践
- 第28章 享元模式
- 28.2 享元模式的定义
- 28.3 享元模式的应用
- 28.4 享元模式的扩展
- 28.5 最佳实践
- 第29章 桥梁模式
- 29.2 桥梁模式的定义
- 29.3 桥梁模式的应用
- 29.4 最佳实践
- 第三部分 谁的地盘谁做主 ——设计模式PK
- 第30章 创建类模式大PK
- 30.1 工厂方法模式VS建造者模式
- 30.2 抽象工厂模式VS建造者模式
- 第31章 结构类模式大PK
- 31.1 代理模式VS装饰模式
- 31.2 装饰模式VS适配器模式
- 第32章 行为类模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS状态模式
- 32.3 观察者模式VS责任链模式
- 第33章 跨战区PK
- 33.1 策略模式VS桥梁模式
- 33.2 门面模式VS中介者模式
- 33.3 包装模式群PK
- 第四部分 完美世界 ——设计模式混编
- 第34章 命令模式+责任链模式
- 34.2 混编小结
- 第35章 工厂方法模式+策略模式
- 35.2 混编小结
- 第36章 观察者模式+中介者模式
- 36.2 混编小结
- 第五部分 扩展篇
- 第37章 MVC框架
- 37.2 最佳实践
- 第38章 新模式
- 38.1 规格模式
- 38.2 对象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空对象模式
- 附录 23种设计模式彩图