装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供比继承更有弹性的替代方案。就增加功能来说,Decorator模式相比生成子类更为灵活。
相信大家现在对装饰者模式都应该很清楚了吧!那么,就像我们在前面的文章里反复强调的一样,设计原则远比模式重要,学习设计模式的同时一定要注意体会设计原则的应用。这里我们再来看看装饰者模式里都符合那些主要的设计原则。
1、 Identify the aspects of your application that vary and separate them from what stays the same. (找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。)
在装饰者模式的应用场景里变化的部分是Component的扩展功能。使用Decorator模式可以很好地将装饰者同被装饰者完全隔离开,我们可以任意改变ConcreteComponent或ConcreteDecorator,它们之间不会有任何相互影响。
2、 Program to an interface,not an implementation.(面向接口编程,而不要面向实现编程。). 多用组合,少用继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
Component和Decorator都是抽象类实现,其实相当于起到很好的接口隔离作用,在运行时具体操作的也是Component类型的变量引用,这完全是面向接口编程的。
3、 Favor composition over inheritance.(优先使用对象组合,而非类继承)
装饰者模式最成功的地方就是合理地使用了对象组合,通过组合灵活地扩展了Component的功能,所有的扩展的功能都是通过组合而非继承获得的,这从根本上决定了这种实现是高内聚低耦合的。
4、 Classes should be open for extension, but closed for modification (类应该对扩展开发,对修改关闭)
这是大名鼎鼎的OCP原则,我们在这个系列的第一篇【模式和原则】里就有专门的介绍。在装饰者模式里充分体现了OCP原则,在需要扩展Component的功能的时候,只需要实现一个新的特定的ConcreteDecorator即可,这完全是一种增量开发,不会对原来代码造成任何影响,对用户代码完全是透明的。
1. 装饰者和被装饰对象有相同的超类型。
2. 可以用一个或多个装饰者包装一个对象。
3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。
4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。
6. 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。
7. 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。
1. 如果只有一个Concrete Component类而没有抽象的Component接口时,可以让Decorator继承Concrete Component。
2. 如果只有一个Concrete Decorator类时,可以将Decorator和Concrete Decorator合并。
以下情况使用Decorator模式
1. 需要扩展一个类的功能,或给一个类添加附加职责。
2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
模式不是万能的,我们要用好设计模式来解决我们的实际问题,就必须熟知模式的应用场景和优缺点:
1、 通过组合而非继承的方式,实现了动态扩展对象的功能的能力。
2、 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
3、 充分利用了继承和组合的长处和短处,在灵活性和扩展性之间找到完美的平衡点。
4、 装饰者和被装饰者之间虽然都是同一类型,但是它们彼此是完全独立并可以各自独立任意改变的。
5、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
6、 Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
7、 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合
1、 装饰链不能过长,否则会影响效率。
2、 因为所有对象都是Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),也就是说,通过继承建立的关系总是脆弱地,如果基类改变,势必影响对象的内部,而通过组合(Decoator HAS A Component)建立的关系只会影响被装饰对象的外部特征。
3、只在必要的时候使用装饰者模式,否则会提高程序的复杂性,增加系统维护难度。
4、 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
5、装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
6、装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。
修饰者模式至少有两个关键利益及两个责任(liability):
再说说提到的动态和静态的问题,所谓动态是说可以在系统运行时(RunTime)动态给对象增加其它职责而不需要修改代码或重新编译;所谓静态是说必须通过调整代码(DesignTime)才能给对象增加职责,而且系统还需要重新编译;从具体技术层面来说,对象的组合和继承正好对应于前面的动态和静态,因为通过对象组合建立的交互关系不是在代码中(DesignTime)固定死的,而是在运行时(RunTime)动态组合的;而通过继承建立的关系是僵硬的难以改变的,因为它是在代码中(DesignTime)固定死了的,根本不存在运行时(RunTime)改变的可能。换个角度说:我们应该多使用对象组合来保持系统的运行时扩展性,尽量少使继承,因为继承让程序变得僵硬!这句话听着是不是很熟悉啊?恩!这就是我们前面文章里提过多次的一个设计原则:Favor composition over inheritance.(优先使用对象组合,而非类继承),更多的就不需要再解释了吧?
首先说一下:本文中很多内容均从网上摘抄而来,看到好的总结,就复制粘贴保留了下来,没有给出出处,很不好意思!下面是自己写的一个例子:
这个例子是用食物来说明:煎饼卷菜,煎饼可以配很多菜,然后算出总的价格,每种蔬菜都一个价格:
先看component接口:
public Interface IFood { public double mPrice; public String mFoodName; abstract String getPrice(); abstract double cost(); }
public class JianBing implements IFood { public JianBing () { mPrice = 1.5; mFoodName = "煎饼"; } @Override public double cost() { // TODO Auto-generated method stub return mPrice ; } @Override String getPrice() { // TODO Auto-generated method stub return "煎饼: + mPrice; } }
public abstract class CondimentDecorator implements IFood{ IFood iFood; public CondimentDecorator (IFood iFood) { this.iFood = iFood; } public double cost () { return mPrice + iFood.cost(); } }
package cn.test; public class DoufuChuan extends CondimentDecorator{ public DoufuChuan(IFood iFood) { super(iFood); mPrice = 1.0; } @Override String getPrice() { // TODO Auto-generated method stub return iFood.getPrice()+" "+"豆腐串:"+mPrice; } }
public class Huotuichang extends CondimentDecorator{ public Huotuichang(IFood iFood) { super(iFood); mPrice = 2.0; } @Override String getPrice() { // TODO Auto-generated method stub return iFood.getPrice()+"/n"+"火腿肠:"+mPrice; } }
public class DecoratorTest { public static void main(String [] args) { IFood iFood = new JianBing(); iFood = new DoufuChuan(iFood); iFood = new Huotuichang(iFood); System.out.println(iFood.getPrice()); System.out.println(“一共花费:”+iFood.cost()); } }
最后结果:
煎饼:1.5 豆腐串:1.0 火腿肠:2.0 一共花费:4.5
如果没有想明白装饰者模式的好处,可以试着再加一个concreteComponent,再加一个烧饼呢,烧饼也可以搭配各种菜,当然在这里只列举了两种。用装饰者是不是方便很多啊?
装饰者模式体现了【对扩展开放,对修改关闭】的原则,很好的解决了类爆炸以及后期的拓展问题。在java当中,IO包中的输入输出流就是用装饰者模式创建的:java的 IO包中的输入输出六都是基于四个抽象类创建的:字节处理流 InputStream,OutputStream,字符处理流 Reader,Writer。
基于这四个抽象类,又分出了节点流和处理流两类。
节点流常用的有 FileInputStream,FileOutputStream,FileReader,FileWriter。这些类包括抽象基类就是就是被包装(修饰)的对象。
处理流中的缓冲流,转换流,数据流就是装饰者,通过这些类,可以讲节点流对象很好的转换为处理流对象,方便的进行读写操作。
当然,处理流和节点流分别有共同的接口,那就是上述四种抽象类。