浅学设计模式之装饰者<Decorator>模式

来源:互联网 发布:数据统计工具和方法 编辑:程序博客网 时间:2024/04/28 18:12

定义

            装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供比继承更有弹性的替代方案。就增加功能来说,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. 适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。



 

其类图实现:

clip_image002


模式的简化:

1. 如果只有一个Concrete Component类而没有抽象的Component接口时,可以让Decorator继承Concrete Component。

clip_image004

2. 如果只有一个Concrete Decorator类时,可以将Decorator和Concrete Decorator合并。

clip_image006

 

适用性:

以下情况使用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):

  1. 比静态继承更有弹性:修饰者模式提供一个较静态(多重)继承更有弹性的方式附加责任到对象上。使用修饰者责任可以在执行期简单的附加或取消,相对的继承需要为每一个新的附加责任建立一个新的类(如BorderedScrollableTextView、BorderedTextView等),如此会造成使用许多类及增加系统复杂度。更进一步;为特定的Component类提供不同的修饰者类让你比较及混合责任。
  2. 避免在层级架构中增加外貌装载(feature-laden《译注:在类中存有许多有关显示外貌的Component或属性等》)类:修饰者提供一个你要才有(pay-as-you-go)的方式附加责任。相对的不去在一个复杂客制化类中提供一所有可预测外貌;而定义一个简单类并在一个修饰者类逐步增加功能,其结果是一个应用系统无须准备一些不会使用到的外貌。同时也容易从他们所扩充的对象类定义一个新的修饰者类;甚至不在预测内。扩充一个复杂类需要揭露与你要附加责任无关的部分。
  3. 修饰者及其Component不是同一个:修饰者就像透明的围墙(transparent enclosure),但从一个对象界定(identity)的观点;一个被修饰的对象与其本身并不需要去区分(identical),因此当你使用修饰者无区依赖对象界定。
  4. 有许多小对象:使用修饰这设计时常常产生在一个系统中组合看起来很像的许多小对象的结果。这些对象的差一点只是互动(interconnect)的方式;而非他的类或者其内不变量的值。虽然只要你了解他们就可以方便客制系统;但是可能难以学习及除错。
  5. 接口一致(conformance):修饰者对象的接口必须与其修饰的对象接口一致具体修饰指(ConcreteDecorator)类必须继承一个公用(common)类。
  6. 忽略(omitting)抽象修饰者类:当你只是附加单一责任时无须定义一个抽象修饰者类,这种情形一般是当你使用现存的类层级架构而非构建一个新的,因此你可以将修饰者传递请求到Component的责任融合(merge)至具体修饰者。
  7. 保持轻量(lightweight)的Component类:要确保接口一致,Component及修饰者必须自一个公用Component类继承,保持这个公用Component类轻量是很重要的,即必须专注于定义一个接口而非储存数据。数据责任的定义必须延缓至子类,否则太复杂的Component类会在数量上使用太过于重(heavyweight)。在Component类中放置太多的功能同时会增加其子类为许多没有必要的外表付出代价的可能性。
  8.   改变对象的外表(skin)或其内部(guts):我们可以想象修饰者就像对象的可以改变行为外表,另一种替代方式是改变其内部,策略模式(Strategy)就是改变其内部的作法。当Component类在本质上(intrinsically)太重(heavyweight)时策略模式是一个比较适当的选择,因为以修饰者模式实现成本太高。在策略模式中组件传递(forward)他的部分行为给另外的策略对象,策略模式让我们以替代的策略对象改变或扩充组件的功能。


         再说说提到的动态和静态的问题,所谓动态是说可以在系统运行时(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();}

接下来是concreteComponent类:煎饼类:

public class JianBing implements IFood {public JianBing () {mPrice = 1.5;mFoodName = "煎饼";}@Overridepublic double cost() {// TODO Auto-generated method stubreturn mPrice ;}@OverrideString getPrice() {// TODO Auto-generated method stubreturn "煎饼: + mPrice;}}

后面是Decorator:
public abstract class CondimentDecorator implements IFood{IFood iFood;public CondimentDecorator (IFood iFood) {this.iFood = iFood;}public double cost () {return mPrice + iFood.cost();}}

        最后是concreteDecorator:

package cn.test;public class DoufuChuan extends CondimentDecorator{public DoufuChuan(IFood iFood) {super(iFood);mPrice = 1.0;}@OverrideString getPrice() {// TODO Auto-generated method stubreturn iFood.getPrice()+" "+"豆腐串:"+mPrice;}}

public class Huotuichang extends CondimentDecorator{public Huotuichang(IFood iFood) {super(iFood);mPrice = 2.0;}@OverrideString getPrice() {// TODO Auto-generated method stubreturn 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。这些类包括抽象基类就是就是被包装(修饰)的对象。

         处理流中的缓冲流,转换流,数据流就是装饰者,通过这些类,可以讲节点流对象很好的转换为处理流对象,方便的进行读写操作。

         当然,处理流和节点流分别有共同的接口,那就是上述四种抽象类。


原创粉丝点击