软件设计模式之(二)装饰者模式

转载自 :  http://blog.csdn.net/chenyujing1234

 例子代码:(编译工具:Eclipse)

http://www.rayfile.com/zh-cn/files/1291b5bd-9418-11e1-b6a1-0015c55db73d/

参考书籍: <<软件秘笈-----设计模式那点事>>

 

装饰者模式(Decorator ['dekəreitə]  Pattren),是在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能,它是通过创建一个包装对象,也就是

装饰来包裹真实的对象

 

使用装饰者模式的时候需要注意以下几点内容:
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用。
(3) 装饰对象接受所有来自客户端的请求,并把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。 这样就确保了在运行时,不用修改给定的对象的结构就可以在外部增加附加功能。
在面向对象的设计中,通常是通过继承来实现对给定的类的功能扩展,然而,装饰者模式不需要子类,可以在应用程序运行时动态扩展功能
 

1、一般化分析

分析一下染色馒头的制作过程:
(1)需要生产一个正常馒头;
(2)为了节省成本(不使用玉米面),使用染色剂加入到正常馒头中;
(3)通过和面机搅拌,最后生产出“玉米馒头”。
 
 
 
[java]  view plain copy
  1. /** 
  2.  * 馒头加工接口 
  3.  *  
  4.  * @author 
  5.  *  
  6.  */  
  7. public interface IBread {  
  8.     // 准备材料  
  9.     public void prepair();  
  10.   
  11.     // 和面  
  12.     public void kneadFlour();  
  13.   
  14.     // 蒸馒头  
  15.     public void steamed();  
  16.   
  17.     /** 
  18.      * 加工馒头方法 
  19.      */  
  20.     public void process();  
  21. }  

[java]  view plain copy
  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. }  

[java]  view plain copy
  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. }  

[java]  view plain copy
  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)不能复用已有的类,造成子类过多。
 

2、下面我们使用装饰者模式重新实现染色馒头的实例!

 
使用装饰者的静态类图,结构如图所示。
 
 
(1)为了装饰正常馒头NormalBread,我们需要一个正常馒头一样的抽象装饰者:AbstractBread,
该类和正常馒头类NormalBread一样实现IBread馒头接口,不同的是该抽象类含有一个IBread接口类型的私有属性bread,
然后通过构造方法,将外部IBread接口类型对象传入。
[java]  view plain copy
  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, 含有修改行为:添加柠檬黄的着色剂。
[java]  view plain copy
  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
[java]  view plain copy
  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、生产甜玉米馒头。
首先创建一个正常馒头,然后使用甜蜜素装饰馒头,之后再用柠檬黄的着色剂装饰馒头,最后加工馒头。
 
[java]  view plain copy
  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. }  
运行结果:
[plain]  view plain copy
  1. ====开始装饰馒头!!!  
  2. 准备面粉、水以及发酵粉...  
  3. 添加柠檬黄的着色剂...  
  4. 添加甜蜜素...  
  5. 和面...  
  6. 蒸馒头...香喷喷的馒头出炉了!  
  7. ====装饰馒头结束!!!  

可能你会感觉比较奇怪,我们先使用的是甜蜜素,然后使用的是着色剂,应该先打印甜蜜素,然后再打印着色剂才对?
不用奇怪,这也不矛盾,因为装饰者相当于对原有对象的包装,这就像一个礼品盒,最里面是一个最普通的纸盒,
然后用一般的纸包装起来,最后使用比较漂亮的包装纸包装, 我们最先看到的是最外漂亮的包装纸,其次才是里面的一般包装纸。
 

3、设计原则

(1)封装变化部分
设计模式是封装变化的最好阐释,无论哪一种设计模式针对的都是软件中存在的“变化”部分,然后
用抽象对这些“变化”的部分进行封装。使用抽象的好处在于为软件的扩展提供了很大的方便性。
在装饰者模式中合理地利用了类继承和组合的方式,非常灵活地表达了对象之间的依赖关系。

装饰者模式应用中“变化”的部分是组件的扩展功能,装饰者和被装饰者完全隔离开来,这样我们就可以任意地改变装饰者和被装饰者。

(2)"开-闭"原则

我们在需要对组件进行扩展、增添新的功能行为时,只需要实现一个特定的装饰者即可,这完全是增量修改,

对原有软件功能结构没有影响,对客户端APP来说也是完全透明的,不必关心内部实现细节。

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

在装饰者模式中,装饰者角色就是抽象类实现,面向抽象编程的好处就在于起到了很好的接口隔离作用。在运用时,

我们具体操作的也是抽象类引用,这些显示了面向抽象编程。

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

装饰者模式最成功在于合理地使用了对象组合方式,通过组合灵活地扩展了组件的功能,所有的扩展功能都是通过组合而非继承获得的,

这从根本上决定了是高内聚、低耦合的。

4、使用场合

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

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

 

客户端不会觉得对象在装饰前和装饰后有什么不同,装饰者模式可以在不创建更多子类的情况下,将对象的功能加以扩展,装饰者模式使用原来被装饰的一个子类实例,

把客户端的调用委派到装饰者。

下面我们来看一下装饰者模式的静态类图.使我们对装饰者模式有一个更加清晰的认识

 

A、被装饰者抽象Component:是一个接口或抽象类,是定义的核心对象。

     在装饰者模式中,必然有一个被撮出来最核心、最原始的接口。这个类就是我们需要装饰类的基类。本例中是IBread接口。

B、被装饰者具体实现ConcreteComponent:这是Component类的一个实现类,我们要装饰的就是这个具体的实现类。本例中是NormalBread

C、装饰者Decorator:一般是一个抽象类。它里面有一个指向Component变量的引用。

D、装饰者实现ConcreteDecorator1和ConcreteDecorator2.

4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值