装饰者模式
装饰者模式是java IO流中使用的一个经典模式,本文会简单介绍装饰者模式的原理和解决的问题,并且以一个咖啡demo来演示装饰者模式。
装饰者模式的定义
装饰者模式遵守的设计原则:开闭原则(类应该支持扩展,而拒绝修改)
装饰者模式通过组合的方式扩展对象的特性,这种方式允许我们在任何时候对对象的功能进行扩展甚至是运行时扩展,而若我们用继承来完成对类的扩展只能在编译时实现,可以比较动态地对对象进行扩展,某些情况下,装饰者模式比继承更加灵活。
装饰者模式的一些特征
- 装饰者(decorator)和被装饰(扩展)的对象有着相同的超类。
- 可以用多个装饰者装饰一个对象。
- 可以用装饰过的对象替换代码中的原对象,(因为有相同的超类)
- 装饰者可以在委托(delegate,即调用被装饰的类的成员完成一些工作)被装饰者的行为完成之前或者之后加上他自己的行为
- 一个对象在任何时候都能被装饰,甚至是运行时。
装饰者模式的结构

Component:一般是一个抽象类,表示一组有着某种用途的基类,包含着这些类最基本的特性。
ConcreteComponent:继承自Component,一般是一个有实际用途的类,这个就是直接的被装饰者。
Decorator:继承自Component,装饰者需要实现的接口(可以是抽象类),用来保证装饰者和被装饰者用共同超类,并保证每一个装饰者有一些必须具有的性质。
ConcreteDecorator:继承自Decorator,用来装饰Component类型的类(ConcreteComponent),但是不能装饰抽象类,为其添加新的特性,可以在委托被装饰者的行为完成之前或之后的任意时候。
装饰者模式的实例
一个比较经典的咖啡案例:咖啡馆提供不同种口味的咖啡和咖啡的调料,我们需要一个程序来描述并且计算出任意一种咖啡和任意几种调料搭配在一起的价格。
最初继承的设计
最容易想到的是用继承去实现这个需求,定义一个超类,其中只对cost做一个抽象,而让所有的咖啡和调料品之间的组合都去继承这个超类,这样一个设计的坏处是类会爆炸,并且当一种咖啡中调料要变化的时候,要修改原来的类,并且会造成大量的冗余。

好一点的调料放入超类方案
也可以想到的是将调料作为一个Boolean变量放入超类中,这样真正的咖啡在继承超类的同时也可以设置想配合的不同调料品。但是这样做的一个缺陷点是如果要引入一个新的调料或者丢弃一个原来的调料,则要对超类进行修改,灵活性受到了很大的挑战。
用装饰者模式去做这个需求

这就是装饰者模式解决这个咖啡馆项目的设计图。
- 可以看到有一个超类Drink,这个就相当于我们装饰者模式中的Component,可以看到在这个超类中有描述变量、价格变量和cost方法。同时cost方法是一个抽象方法,而子类去实例化时去初始化对应的描述和价格变量。
对应的代码:
1 | /** |
- 左边的ShortBlack、Decaf等单体咖啡是装饰者模式中的ConcreteComponent,这些子类中都去实现了cost方法,这里注意这个cost方法的实现,因为每个子类都要去实现cost方法,所以可以做一个中间层去实现这个cost方法,对于这里的cost即为返回子类实例化时在构造函数中填充的父类中的price变量值。
1 | /** |
一个单体咖啡子类的写法:
1 | /** |
- Decorator类即装饰者模式中的Decorator,即为装饰者类的一个抽象,这个类由装饰者模式的定义结构我们可以知道也是继承了Component也就是Drink类的,同时,它也定义了一个Drink类型的变量,这里即用父类定义了要装饰的单体咖啡。因为它继承自Drink类,所以它也要去实现抽象方法cost方法,这里注意因为要在调用时展示一个咖啡的描述和价格时,作为装饰者需要将被装饰者和其他组合在一起的装饰者价格和描述一起展示出来,所以实现是将装饰者的描述和价格 同 被装饰者的描述和价格一起递归显示,所以代码如下,这里的Decorator也相当于真正装饰者的一个抽象或者中间层。这里也要注意装饰者可以装饰一个单体咖啡,也可以装饰一个被装饰过的装饰者。
1 | /** |
- 图中的Soy、Milk类就是其中的调料类,这个要继承Decorator,作用其实就是ConcreteDecorator。这里的作用其实比较简单,其实就是传入被装饰对象(上边也提到这里可以是单体咖啡和被装饰过的装饰者),还有就是把描述和价格填上。
1 | /** |
简单测试
做一个简单的程序测试一下装饰者模式:
1 | public class TestMain { |
java中的装饰者模式
文章一开始就提到了java中的IO流使用了装饰者模式,这里简单的去看一个FilterInputStream类,而它继承的InputStream类即装饰者中的Component类,该类其实就是装饰者的中间层,可以看到其中定义了InputStream的变量引用,而其子类BufferInputStream、DataInputStream、LineNumberInputStream等是ConcreteDecorator。结构如下:
也可以看到代码:
1 | public |
再唠叨几句
装饰者和继承
装饰者模式中用到了继承,但是这里装饰者模式用继承是为了保证装饰者和被装饰者有共同的超类,而不是为了给被装饰的类扩展新的特性,而装饰者模式中新的特性是通过类与类的组合(has-a的关系)而得到的,所以把装饰者模式和继承看做同一类设计思想是不恰当的。
装饰者模式的优点
针对第一种继承的方案:装饰者模式减少了代码的类,只定义了基础的Component和Decorator,而是通过组合的方式实现具体的咖啡的。
针对第二种在超类中定义Boolean变量的方案,装饰者模式提高了灵活性,当你需要加入新的调料或者单体咖啡时,只需定义新的ConcreteComponent和ConcreteDecorator即可。而当你想对一个已有加过调料的咖啡进行再装饰时,也只需要将对应的调料装饰上去即可,价格和描述即可递归显示。
参考
http://www.cnblogs.com/coffeeSS/p/5405787.html
《headFirst 设计模式》
代码在github: https://github.com/zhanglijun1217/design_mode/tree/master/src/main/java/decorator
