24种设计模式--装饰模式【Decorator Pattern】

  装饰模式在中国使用的那实在是多,中国的文化是中庸文化,说话或做事情都不能太直接,需要有技巧的,比如说话吧,你要批评一个人,你不能一上来就说你这个做的不对,那个做的不对,你要先肯定他的成绩,表扬一下优点,然后再指出瑕疵,指出错误的地方,最后再来个激励,你修改了这些缺点后有那些好处,比如你能带更多的小兵,当个小头目等等,否则你一上来就是一顿批评,你瞅瞅看,肯定是不服气,顶撞甚至是直接“此处不养爷,自有养爷处”开溜哇。这是说话,那做事情也有很多,在山寨产品流行之前,假货很是比较盛行的,我在 2002 年买了个手机,当时老板吹的是天花乱坠,承诺这个手机是最新的,我看着也像,壳子是崭新的,包装是崭新的,没有任何瑕疵,就是比正品便宜了一大截,然后我买了,缺钱哪,用来 3 个月,坏了,一送修,检查,说这是个新壳装旧机,我晕!拿一个旧手机的线路板,找个新的外壳、屏幕、包装就成了新手机,装饰模式害人不浅呀!

  我们不说不开心的事情,今天举一个什么例子呢?就说说我上小学的的糗事吧。我上小学的时候学习成绩非常的差,班级上 40 多个同学,我基本上都是在排名 45 名以后,按照老师给我的定义就是“不是读书的料”,但是我老爸管的很严格,明知道我不是这块料,还是往赶鸭子上架,每次考试完毕我都是战战兢兢的,“竹笋炒肉”是肯定少不了的,能少点就少点吧,肉可是自己的呀。四年级期末考试考完,学校出来个很损的招儿(这招儿现在很流行的),打印出成绩单,要家长签字,然后才能上五年级,我那个恐惧呀,不过也就是几秒钟的时间,玩起来什么都忘记了。

  我们先看看这个成绩单的类图:

  成绩单的抽象类,然后有一个四年级的成绩单实现类,先看抽象类:

 1 package com.pattern.decorator;
 2 
 3 /**
 4  * 成绩单的抽象类
 5  * @author http://www.cnblogs.com/initial-road/
 6  *
 7  */
 8 public abstract class SchoolReport {
 9     
10     // 成绩单主要展示的就是你的成绩情况
11     public abstract void report();
12     
13     // 成绩单要家长签字,这个是最要命的
14     public abstract void sign(String name);
15     
16 }
17 
18 // 然后看我们的实现类 FouthGradSchoolReport:
19 
20 package com.pattern.decorator;
21 
22 /**
23  * 四年级的成绩单,这个是我们学校第一次实施,以前没有干过
24  * 这种缺德事
25  * @author http://www.cnblogs.com/initial-road/
26  *
27  */
28 public class FouthGradeSchoolReport extends SchoolReport {
29 
30     // 我的成绩单
31     public void report() {
32         // 成绩单的格式是这样子的
33         System.out.println("尊敬 的XXX家长:");
34         System.out.println(" .......");
35         System.out.println(" 语文 62  数学 65  体育 98  自然 63");
36         System.out.println(" .......");
37         System.out.println("   家长签名:      ");
38     }
39 
40     // 家长签名
41     public void sign(String name) {
42         System.out.println("家长签名为:" + name);
43     }
44 
45 }

  成绩单出来,你别看什么 62,65 之类的成绩,你要知道在小学低于 90 分基本上就是中下等了,唉,爱学习的人太多了!怎么着,那我把这个成绩单给老爸看看?好,我们修改一下类图,成绩单给老爸看:

  老爸开始看成绩单,这个成绩单可是最真实的,啥都没有动过,原装,看 Father 类:

 1 package com.pattern.decorator;
 2 
 3 /**
 4  * 老爸看成绩单了
 5  * @author http://www.cnblogs.com/initial-road/
 6  *
 7  */
 8 public class Father {
 9     
10     public static void main(String[] args) {
11         // 成绩单拿过来
12         SchoolReport sr = new FouthGradeSchoolReport();
13         
14         // 看成绩单
15         sr.report();
16         
17         // 签名? 休想!
18     }
19 
20 }

  就这成绩还要我签字?!老爸就开始找笤帚,我的屁股已经做好了准备,肌肉要绷紧,要不那个太疼了!哈哈,幸运的是,这个不是当时的真实情况,我没有直接把成绩单交给老爸,而是在交给他之前做了点技术工作,我要把成绩单封装一下,封装分类两步走:

  第一步: 跟老爸说各个科目的最高分,语文最高是 75,数学是 78,自然是 80,然老爸觉的我成绩与最高分数相差不多,这个是实情,但是不知道是什么原因,反正期末考试都考的不怎么样,但是基本上都集中在 70 分以上,我这 60 多分基本上还是垫底的角色;

  第二步:在老爸看成绩单后,告诉他我是排名第 38 名,全班,这个也是实情,为啥呢?有将近十个同学退学了!这个情况我是不说的。不知道是不是当时第一次发成绩单,学校没有考虑清楚,没有写上总共有多少同学,排名第几名等等,反正是被我钻了个空子。

  那修饰是说完了,我们看看类图如何修改:

  我想这是你最容易想到的类图,通过直接增加了一个子类,重写 report 方法,很容易的解决了这个问题,是不是这样?是的,确实是,确实是一个很好的办法,我们来看具体的实现:

 1 package com.pattern.decorator;
 2 
 3 /**
 4  * 对这个成绩单进行美化
 5  * Sugar这个词太好了,名词是糖的意思,动词就是美化
 6  * 给你颗糖你还不美去
 7  * @author http://www.cnblogs.com/initial-road/
 8  *
 9  */
10 public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
11     
12     // 首先要定义你要美化的方法,先给老爸说学校最高成绩
13     private void reportHignScore(){
14         System.out.println("这次考试语文最高是75,数学是78,自然是80");
15     }
16     
17     // 在老爸看完成绩单后,我在汇报学校的排名情况
18     private void reportSort(){
19         System.out.println("我是排名第38名...");
20     }
21     
22     // 由于汇报的内容已经发生变更,所以要重写父类
23     public void report(){
24         // 先说最高成绩
25         this.reportHignScore();
26         
27         // 然后告诉老爸学习,学校排名
28         this.reportSort();
29         
30     }
31 }
32 
33 // 然后 Father 类稍做修改就可以看到美化后的成绩单,看代码如下:
34 
35 package com.pattern.decorator;
36 
37 /**
38  * 老爸看成绩单了
39  * @author http://www.cnblogs.com/initial-road/
40  *
41  */
42 public class Father {
43     
44     public static void main(String[] args) {
45         // 美化过的成绩单拿过来
46         SchoolReport sr = new SugarFouthGradeSchoolReport();
47         
48         // 看成绩单
49         sr.report();
50         
51         // 然后老爸,一看,很开心,就签名了
52         sr.sign("老爹");
53     }

54 }

  通过继承确实能够解决这个问题,老爸看成绩单很开心,然后就给签字了,但是现实的情况很复杂的,可能老爸听我汇报最高成绩后,就直接乐开花了,直接签名了,后面的排名就没必要了,或者老爸要先听排名情况,那怎么办?继续扩展类?你能扩展多少个类?这还是一个比较简单的场景,一旦需要装饰的条件非常的多,比如20 个,你还通过继承来解决,你想想的子类有多少个?你是不是马上就要崩溃了!

  好,你也看到通过继承情况确实出现了问题,类爆炸,类的数量激增,光写这些类不累死你才怪,而且还要想想以后维护怎么办,谁愿意接收这么一大堆类的维护哪?并且在面向对象的设计中,如果超过 2 层继承,你就应该想想是不是出设计问题了,是不是应该重新找一条道了,这是经验值,不是什么绝对的,继承层次越多你以后的维护成本越多,问题这么多,那怎么办?好办,装饰模式出场来解决这些问题,我们先来看类图:

  增加一个抽象类和两个实现类,其中 Decorator 的作用是封装 SchoolReport 类,看源代码:

  1 package com.pattern.decorator;
  2 
  3 /**
  4  * 装饰类,我要把我的成绩单装饰一下
  5  * @author http://www.cnblogs.com/initial-road/
  6  *
  7  */
  8 public class Decorator extends SchoolReport {
  9     // 首先我要知道是哪个成绩单
 10     private SchoolReport sr;
 11     
 12     public Decorator(SchoolReport sr){
 13         this.sr = sr;
 14     }
 15     
 16     // 成绩单还是要被看到的
 17     public void report() {
 18         this.sr.report();
 19     }
 20 
 21     // 看完成绩单还是要签名的
 22     public void sign(String name) {
 23         this.sr.sign(name);
 24     }
 25 
 26 }
 27 
 28 // Decorator 抽象类的目的很简单,就是要让子类来对封装 SchoolReport 的子类,怎么封装?重写report 方法!先看 HighScoreDecorator 实现类:
 29 
 30 package com.pattern.decorator;
 31 
 32 /**
 33  * 我要把我的最高成绩告诉老爸
 34  * @author http://www.cnblogs.com/initial-road/
 35  *
 36  */
 37 public class HignScoreDecorator extends Decorator {
 38 
 39     // 构造函数
 40     public HignScoreDecorator(SchoolReport sr) {
 41         super(sr);
 42     }
 43     
 44     // 我要汇报最高成绩
 45     private void reportHignScor(){
 46         System.out.println("这次考试语文最高是75,数学是78,自然是80");
 47     }
 48     
 49     // 最高成绩我要在老爸看成绩单前告诉他,否则等他一看,就抡起扫帚要揍我,我那还有机会说呀
 50     public void report(){
 51         this.reportHignScor();
 52         super.report();
 53     }
 54     
 55 }
 56 
 57 // 重写了 report 方法,先调用具体装饰类的装饰方法 reportHighScore,然后再调用具体构件的方法,我们再来看怎么回报学校排序情况 SortDecorator 代码:
 58 
 59 package com.pattern.decorator;
 60 
 61 /**
 62  * 学校排名的情况汇报
 63  * @author http://www.cnblogs.com/initial-road/
 64  *
 65  */
 66 public class SortDecorator extends Decorator {
 67     
 68     // 构造函数
 69     public SortDecorator(SchoolReport sr) {
 70         super(sr);
 71     }
 72     
 73     // 告诉老爸学校的排名情况
 74     private void reportSort(){
 75         System.out.println("我是排名第38名...");
 76     }
 77     
 78     // 老爸看完成绩单后在告诉他,加强作用
 79     public void report(){
 80         super.report();
 81         this.reportSort();
 82     }
 83     
 84 }
 85 
 86 // 然后看看我老爸怎么看成绩单的:
 87 
 88 package com.pattern.decorator;
 89 
 90 /**
 91  * 老爸看成绩单了
 92  * @author http://www.cnblogs.com/initial-road/
 93  *
 94  */
 95 public class Father {
 96 
 97     public static void main(String[] args) {
 98         // 成绩单拿过来
 99         // 原装的成绩单
100         SchoolReport sr;
101         sr = new FouthGradeSchoolReport();
102         
103         // 加了最高分说明的成绩单
104         sr = new HignScoreDecorator(sr);
105         
106         // 又加了成绩排名的说明
107         sr = new SortDecorator(sr);
108         
109         // 看成绩单
110         sr.report();
111         
112         //然后老爸,一看,很开心,就签名了
113         sr.sign("老爹");
114         
115     }
116 }

  老爸一看成绩单,听我这么一说,非常开心,儿子有进步呀,从 40 多名进步到 30 多名,进步很大,躲过了一顿海扁。

  这就是装饰模式,装饰模式的通用类图如下:

  看类图,Component 是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象,比如上面的成绩单,记住在装饰模式中,必然有一个被提取出来最核心、最原始、最基本的接口或抽象类,就是 Component。

  ConcreteComponent 这个是最核心、最原始、最基本的接口或抽象类的实现,你要装饰的就是这个东东。

  Decorator 一般是一个抽象类,做什么用呢?实现接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的属性里必然有一个 private 变量指向Component。

  ConcreteDecoratorA 和 ConcreteDecoratorB 是两个具体的装饰类,你要把你最核心的、最原始的、最基本的东西装饰城啥东西,上面的例子就是把一个比较平庸的成绩单装饰成家长认可的成绩单。

  装饰模式是对继承的有力补充,你要知道继承可不是万能的,继承可以解决实际的问题,但是在项目中你要考虑诸如易维护、易扩展、易复用等,而且在一些情况下(比如上面那个成绩单例子)你要是用继承就会增加很多了类,而且灵活性非常的差,那当然维护也不容易了,也就是说装饰模式可以替代继承,解决我们类膨胀的问题,你要知道继承是静态的给类增加功能,而装饰模式则是动态的给增加功能,你看上面的那个例子,我不想要 SortDecorator 这层的封装也很简单呀,直接在 Father 中去掉就可以了,如果你用继承就必须修改程序。

  装饰模式还有一个非常好的优点,扩展性非常好,在一个项目中,你会有非常多因素考虑不到,特别是业务的变更,时不时的冒出一个需求,特别是提出一个令项目大量延迟的需求时候,那种心情是…,真想骂娘!装饰模式可以给我们很好的帮助,通过装饰模式重新封装一个类,而不是通过继承来完成,简单点说,三个继承关系 Father,Son,GrandSon 三个类,我要再 Son 类上增强一些功能怎么办?我想你会坚决的顶回去!不允许,对了,为什么呢?你增强的功能是修改 Son 类中的方法吗?增加方法吗 ?对 GrandSon的影响哪?特别是 GrandSon 有多个的情况,你怎么办?这个评估的工作量就是够你受的,所以这个是不允许的,那还是要解决问题的呀,怎么办?通过建立 SonDecorator 类来修饰 Son,等于说是创建了一个新的类,这个对原有程序没有变更,通过扩充很好的完成了这次变更。

posted @ 2015-01-08 21:21  jay_zhang  阅读(320)  评论(0编辑  收藏  举报