第17章 装饰模式
17.1 罪恶的成绩单
“中庸”是中国儒教文化的集中体现,说话或做事情都不能太直接,需要有技巧。比如谈话,如果你要批评某个人,你不能一上来就说他这做得不对,那也做得不对,你要先肯定他的成绩,表扬一下优点;然后再指出不足,指出错误的地方,最后再来点激励,你修改了这些缺点后有哪些好处,比如你能带更多的小兵、升职等。如果你一上来就是一顿批评,你瞅瞅看,对方肯定是不服气,甚至是顶撞你说:“此处不养爷,自有养爷处”,于是甩门而去。
这是说话,做事情也是一样。在山寨产品流行之前,假货也是比较“盛行”的。本人2002年买了一部手机,当时老板吹得天花乱坠,承诺这部手机是最新的,我看着也像,壳子是崭新的,包装是崭新的,没有任何瑕疵,就是比正品便宜了很多,于是我买了,因为缺钱啊!用了3个月,坏了,送修检查,结果诊断出这是新壳装旧机,我晕!拿一部旧手机的线路板,找个新的外壳、屏幕、包装就成了新手机,害人不浅啊!
我们不说不开心的事情,今天以什么例子为开场白呢?就说说我上小学的糗事吧。我上小学的时候学习成绩非常差,班级上有40多个同学,我基本上都是排在45名以后,按照老师给我的评价就是:“不是读书的料”。但是我父亲管得很严格,明知道我不是这块料,还是“赶鸭子上架”,每次考完试我都战战兢兢,“竹笋炒肉”是肯定少不了的,但是能少点就少点吧,因为肉可是自己的。四年级期末考试考完,学校出来个很损的招儿(这招儿现在很流行的),打印出成绩单,要家长签字,然后才能上五年级,我那个恐惧呀,不过也就是几秒钟的时间,玩起来什么都忘记了。我们做架构,做设计,任何值得我们回忆的事件都可以通过设计记录下来。当然了,这份成绩单的事情也是可以通过类图表示的,如图17-1所示。
![](https://box.kancloud.cn/2016-08-14_57b00365252a9.jpg)
图17-1 成绩单类图
成绩单的抽象类,然后有一个四年级的成绩单实现类,So Easy,我们先来看抽象类,如代码清单17-1所示。
代码清单17-1 抽象成绩单
public abstract class SchoolReport {
//成绩单主要展示的就是你的成绩情况
public abstract void report();
//成绩单要家长签字,这个是最要命的
public abstract void sign();
}
有抽象类了,我们再来看看具体的四年级成绩单FouthGradeSchoolReport,如代码清单17-2所示。
代码清单17-2 四年级成绩单
public class FouthGradeSchoolReport extends SchoolReport {
//我的成绩单
public void report() {
//成绩单的格式是这个样子的
System.out.println("尊敬的XXX家长:");
System.out.println(" ......");
System.out.println(" 语文 62 数学65 体育 98 自然 63");
System.out.println(" .......");
System.out.println(" 家长签名: ");
}
//家长签名
public void sign(String name) {
System.out.println("家长签名为:"+name);
}
}
成绩单出来,你别看什么62、65之类的成绩,你要知道,在小学低于90分基本上就是中下等了,悲哀呀,爱学习的人咋就那么多!怎么着,那我把这个成绩单给老爸看看?好,我们修改一下类图,成绩单给老爸看,如图17-2所示。
![](https://box.kancloud.cn/2016-08-14_57b003653a9d4.jpg)
图17-2 老爸查看成绩单类图
老爸开始看成绩单,这个成绩单可是最真实的,啥都没有动过,原装,Father类如代码清单17-3所示。
代码清单17-3 老爸查看成绩单
public class Father {
public static void main(String[] args) {
//把成绩单拿过来
SchoolReport sr = new FouthGradeSchoolReport();
//看成绩单
sr.report();
//签名?休想!
}
}
运行结果如下:
尊敬的XXX家长:
......
语文 62 数学65 体育 98 自然 63
.......
家长签名:
就这成绩还要我签字?!老爸就开始找扫帚,我开始做准备:深呼吸,绷紧肌肉,提臀收腹。哈哈,幸运的是,这个不是当时的真实情况,我没有直接把成绩单交给老爸,而是在交给他之前做了点技术工作,我要把成绩单封装一下,封装分类两步来实现,如下所示。
● 汇报最高成绩
跟老爸说各个科目的最高分,语文最高是75,数学是78,自然是80,然后老爸觉得我的成绩与最高分数相差不多,考的还是不错的嘛!这个是实情,但是不知道是什么原因,反正期末考试都考得不怎么样,但是基本上都集中在70分以上,我这60多分基本上还是垫底的角色。
● 汇报排名情况
在老爸看完成绩单后,告诉他我在全班排第38名,这个也是实情,为啥呢?有将近十个同学退学了!这个情况我是不会说的。不知道是不是当时第一次发成绩单时学校没有考虑清楚,没有写上总共有多少同学,排第几名,反正是被我钻了个空子。
那修饰是说完了,我们看看类图如何修改,如图17-3所示。
![](https://box.kancloud.cn/2016-08-14_57b003654fc2d.jpg)
图17-3 修饰成绩单
我想这是大家最容易想到的类图,通过直接增加了一个子类,重写report方法,很容易地解决了这个问题,是不是这样?是的,这确实是一个比较好的办法,我们来看具体的实现,如代码清单17-4所示。
代码清单17-4 修饰成绩单
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
//首先要定义你要美化的方法,先给老爸说学校最高成绩
private void reportHighScore(){
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
//在老爸看完毕成绩单后,我再汇报学校的排名情况
private void reportSort(){
System.out.println("我是排名第38名...");
}
//由于汇报的内容已经发生变更,那所以要重写父类
@Override
public void report(){
this.reportHighScore(); //先说最高成绩
super.report(); //然后老爸看成绩单
this.reportSort(); //然后告诉老爸学习学校排名
}
}
然后对Father类稍做修改就可以看到美化后的成绩单,如代码清单17-5所示。
代码清单17-5 老爸查看修饰后的成绩单
public class Father {
public static void main(String[] args) {
//把美化过的成绩单拿过来
SchoolReport sr= new SugarFouthGradeSchoolReport();
//看成绩单
sr.report();
//然后老爸,一看,很开心,就签名了
sr.sign("老三"); //我叫小三,老爸当然叫老三
}
}
运行结果如下所示:
这次考试语文最高是75,数学是78,自然是80
尊敬的XXX家长:
......
语文 62 数学65 体育 98 自然 63
.......
家长签名:
我是排名第38名...
家长签名为:老三
通过继承确实能够解决这个问题,老爸看成绩单很开心,然后就给签字了,但现实的情况是很复杂的,可能老爸听我汇报最高成绩后,就直接乐开花了,直接签名了,后面的排名就没必要看了,或者老爸要先看排名情况,那怎么办?继续扩展?你能扩展多少个类?这还是一个比较简单的场景,一旦需要装饰的条件非常多,比如20个,你还通过继承来解决,你想象的子类有多少个?你是不是马上就要崩溃了!
好,你也看到通过继承情况确实出现了问题,类爆炸,类的数量激增,光写这些类不累死你才怪,而且还要想想以后维护怎么办,谁愿意接收这么一大摊本质相似的代码维护工作?并且在面向对象的设计中,如果超过两层继承,你就应该想想是不是出设计问题了,是不是应该重新找一条康庄大道了,这是经验值,不是什么绝对的,继承层次越多以后的维护成本越多,问题这么多,那怎么办?好办,我们定义一批专门负责装饰的类,然后根据实际情况来决定是否需要进行装饰,类图稍做修正,如图17-4所示。
![](https://box.kancloud.cn/2016-08-14_57b0036564b76.jpg)
图17-4 增加专门的装饰类图
增加一个抽象类和两个实现类,其中Decorator的作用是封装SchoolReport类,如果大家还记得代理模式,那么很容易看懂这个类图,装饰类的作用也就是一个特殊的代理类,真实的执行者还是被代理的角色FouthGradeSchoolReport,如代码清单17-6所示。
代码清单17-6 修饰的抽象类
public abstract class Decorator extends SchoolReport{
//首先我要知道是哪个成绩单
private SchoolReport sr;
//构造函数,传递成绩单过来
public Decorator(SchoolReport sr){
this.sr = sr;
}
//成绩单还是要被看到的
public void report(){
this.sr.report();
}
//看完还是要签名的
public void sign(String name){
this.sr.sign(name);
}
}
看到没,装饰类还是把动作的执行委托给需要装饰的对象,Decorator抽象类的目的很简单,就是要让子类来封装SchoolReport的子类,怎么封装?重写report方法!先看HighScoreDecorator实现类,如代码清单17-7所示。
代码清单17-7 最高成绩修饰
public class HighScoreDecorator extends Decorator {
//构造函数
public HighScoreDecorator(SchoolReport sr){
super(sr);
}
//我要汇报最高成绩
private void reportHighScore(){
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
//我要在老爸看成绩单前告诉他最高成绩,否则等他一看,就抡起扫帚揍我,我哪里还有机会说啊
@Override
public void report(){
this.reportHighScore();
super.report();
}
}
重写了report方法,先调用具体装饰类的装饰方法reportHighScore,然后再调用具体构件的方法,我们再来看怎么汇报学校排序情况SortDecorator代码,如代码清单17-8所示。
代码清单17-8 排名情况修饰
public class SortDecorator extends Decorator {
//构造函数
public SortDecorator(SchoolReport sr){
super(sr);
}
//告诉老爸学校的排名情况
private void reportSort(){
System.out.println("我是排名第38名...");
}
//老爸看完成绩单后再告诉他,加强作用
@Override
public void report(){
super.report();
this.reportSort();
}
}
我准备好了这两个强力的修饰工具,然后就“毫不畏惧”地把成绩单交给老爸,看看老爸怎么看成绩单的,如代码清单17-9所示。
代码清单17-9 老爸查看修饰后的成绩单
public class Father {
public static void main(String[] args) {
//把成绩单拿过来
SchoolReport sr;
//原装的成绩单
sr = new FouthGradeSchoolReport();
//加了最高分说明的成绩单
sr = new HighScoreDecorator(sr);
//又加了成绩排名的说明
sr = new SortDecorator(sr);
//看成绩单
sr.report();
//然后老爸一看,很开心,就签名了
sr.sign("老三"); //我叫小三,老爸当然叫老三
}
}
老爸一看成绩单,听我这么一说,非常开心,儿子有进步呀,从40多名进步到30多名,进步很大,躲过了一顿海扁。想想看,如果我还要增加其他的修饰条件,是不是就非常容易了,只要实现Decorator类就可以了!这就是装饰模式。
- 前言
- 第一部分 大旗不挥,谁敢冲锋——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种设计模式彩图