A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 程序猿伊桑 初级黑马   /  2017-12-7 23:08  /  763 人查看  /  0 人回复  /   2 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 程序猿伊桑 于 2017-12-9 15:07 编辑

    pppppps:个人博客地址http://www.cnblogs.com/ethan0603/
今天得空整理了一下装饰设计模式(Decorator),所谓的装饰设计模式,其实就是在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真实的对象。很多人会有疑问,为什么要有这个模式呢?首先如果我们想要扩展一个功能,最直接的有两种方法,一种是直接去更改原类,但这在现实中是极为不可取或者说根本就是不现实的;另一种就是写一个类来继承,然后重写方法,实现功能的扩展,但是众所周知,Java中是单继承的,也就是说如果你仅仅是为了实现某一个功能的扩展而重现去创建一个类并使用了唯一的继承资格,这是非常不值得的,而且一旦继承体系过多,会让整个体系显得非常臃肿,这是实际开发中也是很避讳的事情,所以,就有了装饰设计模式!

装饰设计模式有以下特点:

(1)装饰对象和真实对象有相同的接口(这样客户端对象就可以以和真实对象相同的方式和装饰对象交互)。

(2)装饰对象包含一个真实对象的引用。

(3)装饰对象接受所有来自客户端的请求,并把这些请求转发给真实的对象。

(4)装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定的对象的结构就可以在外部增加附加功能。

我们可以用现实中黑心商家生产馒头来作为这个设计模式的例子:

生产一个染色馒头,我们需要1.生产一个正常馒头 2.将染色剂添加到馒头中3.搅面机搅拌,生产出有色的“玉米馒头”。
复制代码

1  /**
2      * 馒头加工接口
3      * @author
4      */  
5     public interface IBread {  
6         // 准备材料  
7         public void prepair();  
8      
9         // 和面  
10         public void kneadFlour();  
11      
12         // 蒸馒头  
13         public void steamed();  
14      
15         /**
16          * 加工馒头方法
17          */  
18         public void process();  
19 }  

复制代码
复制代码

1 /**
2      * 正常馒头的实现
3      *  
4      * @author
5      *  
6      */  
7     public class NormalBread implements IBread {  
8         // 准备材料  
9         public void prepair() {  
10             System.out.println("准备面粉、水以及发酵粉...");  
11         }  
12      
13         // 和面  
14         public void kneadFlour() {  
15             System.out.println("和面...");  
16         }  
17      
18         // 蒸馒头  
19         public void steamed() {  
20             System.out.println("蒸馒头...香喷喷的馒头出炉了!");  
21         }  
22      
23         /**
24          * 加工馒头方法
25          */  
26         public void process() {  
27             // 准备材料  
28             prepair();  
29             // 和面  
30             kneadFlour();  
31             // 蒸馒头  
32             steamed();  
33         }  
34      
35     }  

复制代码
复制代码

1 /**
2      * 染色的玉米馒头
3      *  
4      * @author
5      *  
6      */  
7     public class CornBread extends NormalBread {  
8         // 黑心商贩 开始染色了  
9         public void paint() {  
10             System.out.println("添加柠檬黄的着色剂...");  
11         }  
12      
13         // 重载父类的和面方法  
14         @Override  
15         public void kneadFlour() {  
16             // 在面粉中加入 染色剂 之后才开始和面  
17             this.paint();  
18             // 和面  
19             super.kneadFlour();  
20         }  
21     }  

复制代码
复制代码

1 /**
2      * 甜蜜素馒头
3      *  
4      * @author
5      *  
6      */  
7     public class SweetBread extends NormalBread {  
8         // 黑心商贩 开始添加甜蜜素  
9         public void paint() {  
10             System.out.println("添加甜蜜素...");  
11         }  
12      
13         // 重载父类的和面方法  
14         @Override  
15         public void kneadFlour() {  
16             // 在面粉中加入 甜蜜素 之后才开始和面  
17             this.paint();  
18             // 和面  
19             super.kneadFlour();  
20         }  
21     }  

复制代码

这样子“玉米馒头”就成功生产出来了,这时候如果黑心商家如果又想生产其他颜色的馒头怎么办呢?是的,我们可以使用继承,但是使用继承会有两个不好的点:

(1)父类的依赖程序过高,父类修改会影响到子类的行为。

(2)不能复用已有的类,造成子类过多。

所以这时候就引出了我们的装饰者模式了。

(1)为了装饰正常馒头NormalBread,我们需要一个正常馒头一样的抽象装饰者:AbstractBread,该类和正常馒头类NormalBread一样实现IBread馒头接口,不同的是该抽象类含有一个IBread接口类型的私有属性bread,然后通过构造方法,将外部IBread接口类型对象传入。
复制代码

1 /**
2      * 抽象装饰者
3      *  
4      * @author
5      *  
6      */  
7     public abstract class AbstractBread implements IBread {  
8         // 存储传入的IBread对象  
9         private final IBread bread;  
10      
11         public AbstractBread(IBread bread) {  
12             this.bread = bread;  
13         }  
14      
15         // 准备材料  
16         public void prepair() {  
17             this.bread.prepair();  
18         }  
19      
20         // 和面  
21         public void kneadFlour() {  
22             this.bread.kneadFlour();  
23         }  
24      
25         // 蒸馒头  
26         public void steamed() {  
27             this.bread.steamed();  
28         }  
29      
30         // 加工馒头方法  
31         public void process() {  
32             prepair();  
33             kneadFlour();  
34             steamed();  
35      
36         }  
37 }  

复制代码

AbstractBread类满足了装饰者的要求:和真实对象具有相同的接口;包含一个真实对象的引用;接受所有来自客户端的请求,并反这些请求转发给真实的对象;

可以增加一些附加功能。

(2)创建装饰者。

A、创建染色剂装饰者----CornDecorator.

创建染色装饰者"CornDecorator",继承AbstractBread, 含有修改行为:添加柠檬黄的着色剂。
复制代码

1 /**
2      * 染色的玉米馒头
3      *  
4      * @author
5      *  
6      */  
7     public class CornDecorator extends AbstractBread {  
8      
9         // 构造方法  
10         public CornDecorator(IBread bread) {  
11             super(bread);  
12         }  
13      
14         // 黑心商贩 开始染色了  
15         public void paint() {  
16             System.out.println("添加柠檬黄的着色剂...");  
17         }  
18      
19         // 重载父类的和面方法  
20         @Override  
21         public void kneadFlour() {  
22             // 在面粉中加入 染色剂 之后才开始和面  
23             this.paint();  
24             // 和面  
25             super.kneadFlour();  
26         }  
27     }  

复制代码

这和上面提到的CornBread类的内容是一样的,只是多了一个构造方法。

B、创建甜蜜互装饰者-----SweetDecorator
复制代码

1  /**
2      * 甜蜜素馒头
3      *  
4      * @author
5      *  
6      */  
7     public class SweetDecorator extends AbstractBread {  
8         // 构造方法  
9         public SweetDecorator(IBread bread) {  
10             super(bread);  
11         }  
12      
13         // 黑心商贩 开始添加甜蜜素  
14         public void paint() {  
15             System.out.println("添加甜蜜素...");  
16         }  
17      
18         // 重载父类的和面方法  
19         @Override  
20         public void kneadFlour() {  
21             // 在面粉中加入 甜蜜素 之后才开始和面  
22             this.paint();  
23             // 和面  
24             super.kneadFlour();  
25         }  
26     }  

复制代码

C、生产甜玉米馒头。

首先创建一个正常馒头,然后使用甜蜜素装饰馒头,之后再用柠檬黄的着色剂装饰馒头,最后加工馒头。


复制代码

1 /**
2      * 客户端应用程序
3      *  
4      * @author
5      *  
6      */  
7     public class Client {  
8      
9         /**
10          * @param args
11          */  
12         public static void main(String[] args) {  
13             // 生产装饰馒头  
14             System.out.println("\n====开始装饰馒头!!!");  
15             // 创建普通的正常馒头实例  
16             // 这是我们需要包装(装饰)的对象实例  
17             IBread normalBread = new NormalBread();  
18      
19             // 下面就开始 对正常馒头进行装饰了!!!  
20             // 使用甜蜜素装饰馒头  
21             normalBread = new SweetDecorator(normalBread);  
22             // 使用柠檬黄的着色剂装饰馒头  
23             normalBread = new CornDecorator(normalBread);  
24             // 生产馒头信息  
25             normalBread.process();  
26             System.out.println("====装饰馒头结束!!!");  
27      
28         }  
29      
30     }  

复制代码



运行结果:

    ====开始装饰馒头!!!  

    准备面粉、水以及发酵粉...  

    添加柠檬黄的着色剂...  

    添加甜蜜素...  

    和面...  

    蒸馒头...香喷喷的馒头出炉了!

====装饰馒头结束!!!



3、设计原则

(1)封装变化部分

设计模式是封装变化的最好阐释,无论哪一种设计模式针对的都是软件中存在的“变化”部分,然后用抽象对这些“变化”的部分进行封装。使用抽象的好处在于为软件的扩展提供了很大的方便性。在装饰者模式中合理地利用了类继承和组合的方式,非常灵活地表达了对象之间的依赖关系。装饰者模式应用中“变化”的部分是组件的扩展功能,装饰者和被装饰者完全隔离开来,这样我们就可以任意地改变装饰者和被装饰者。

(2)"开-闭"原则

我们在需要对组件进行扩展、增添新的功能行为时,只需要实现一个特定的装饰者即可,这完全是增量修改,对原有软件功能结构没有影响,对客户端APP来说也是完全透明的,不必关心内部实现细节。

(3)面向抽象编程,不要面向实现编程

在装饰者模式中,装饰者角色就是抽象类实现,面向抽象编程的好处就在于起到了很好的接口隔离作用。在运用时,我们具体操作的也是抽象类引用,这些显示了面向抽象编程。

(4)优先使用对象组合,而非类继承。

装饰者模式最成功在于合理地使用了对象组合方式,通过组合灵活地扩展了组件的功能,所有的扩展功能都是通过组合而非继承获得的,这从根本上决定了是高内聚、低耦合的。

4、使用场合

(1)当我们需要为某个现有的对象动态地增加一个新的功能或职责时,可以考虑使用装饰者模式;

(2)当某个对象的职责经常发生变化或者经常需要动态地增加职责,避免为了适应这样的变化而增加继承子类扩展的方式,因为这种方式会造成子类膨胀的速度过快,难以控制,此时可以使用装饰者模式。

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马