在说明什么是Decorator模式之前,先来看看它有什么优点,通过下面的例子你或许会对它有一个简单的认识
需求背景
设计一个Modem(调制解调器)的层次结构,在这个结构中
(1) Modem基类包含了一些调制解调器常用的功能,比如拨号,音量的控制
(2) 子类一:LoudModem,一般的拨号器在拨号的时候是没有声音的,这种modem在拨号的时候会发出声音
(3) 子类二:ScreenModem,一般的拨号器在拨号的时候是不会把号码显示在屏幕上的,这种modem在拨号的时候会将号码显示在屏幕上
方案一:继承
这是一种比较容易想到的方案,对于简单且稳定的业务场景这或许是个很好的选择,大概的类图如下
BaseModem实现了Modem接口中的方法,然后LoundModem,PrintModem都继承自BaseModem,根据需求分别覆盖dial方法
缺点
当子类的需求发生变化时,例如LoudModem也要求实现print的功能,这时只能修改LoudModem的dial方法,这时就违反了OCP(开放封闭原则)
如果有多个子类,每个子类都要求添加print的功能,那么这些子类的dial方法都需要进行修改,对于软件维护而言这或许是个噩梦的开始
方案二:Decorator模式
分析
将Modem的拨号特性 "Loud""Print" 都作为一个个单独的装饰类,当某个子类需要某个一些特性时,就直接用专门的装饰类来装饰它,且装饰的效果是可以叠加的.
例如 对于一个既有声音又能打印的modem而言,只需要用Loud以及Print这两个装饰器来装饰它既可,如果要想添加新的效果,只需要开发一个新的装饰器,然后用这个装饰器来装饰对应的子类便可,对于已经存在的代码不需要做任何的改动.
类图如下
从类图可以看出,每个Decorator都有一个dial方法,且都包含一个Modem的成员变量,具体的应用请参考下面的代码
样例代码
Modem接口类
package com.eric.designmodel.decorator;
public interface Modem {
public void dial(String number);
public void setSpeakVolumn(int volumn);
public String getPhoneNumber();
public int getSpeakVolumn();
}
BaseModem基类
package com.eric.designmodel.decorator;
public class BaseModem implements Modem {
private String number;
private int volumn;
@Override
public void dial(String number) {
this.number = number;
}
@Override
public void setSpeakVolumn(int volumn) {
this.volumn = volumn;
}
@Override
public String getPhoneNumber() {
return number;
}
@Override
public int getSpeakVolumn() {
return volumn;
}
}
HayesModem 子类
/**
*
*/
package com.eric.designmodel.decorator;
/**
* Description: <br/>
* Program Name:DesignPattern Date:2013-8-21 下午9:26:28
*
* @author Eric
*
* @version 1.0
*/
public class HayesModem extends BaseModem {
}
LoudDecorator装饰类
package com.eric.designmodel.decorator;
/**
* Description:被该装饰器装饰过的Modem对象,在调用dial方法时, volumn会被设置为10<br/>
* Program Name:DesignPattern Date:2013-8-21 下午9:36:45
*
* @author Eric
*
* @version 1.0
*/
public class LoudDecorator implements Modem {
private Modem modem;
public LoudDecorator(Modem modem) {
this.modem = modem;
}
@Override
public void dial(String number) {
modem.setSpeakVolumn(10);
modem.dial(number);
}
@Override
public void setSpeakVolumn(int volumn) {
modem.setSpeakVolumn(volumn);
}
@Override
public String getPhoneNumber() {
return modem.getPhoneNumber();
}
@Override
public int getSpeakVolumn() {
return modem.getSpeakVolumn();
}
}
ScreenDecorator装饰类
package com.eric.designmodel.decorator;
/**
* Description: 被该装饰器装饰过的Modem对象,在调用dial方法时,number会加上区号<br/>
* Program Name:DesignPattern Date:2013-8-21 下午9:35:01
*
* @author Eric
*
* @version 1.0
*/
public class ScreenDecorator implements Modem {
public static final String NUMBER_PREFIX = "025+";
private Modem modem;
public ScreenDecorator(Modem modem) {
this.modem = modem;
}
@Override
public void dial(String number) {
modem.dial(NUMBER_PREFIX + number);
}
@Override
public void setSpeakVolumn(int volumn) {
modem.setSpeakVolumn(volumn);
}
@Override
public String getPhoneNumber() {
return modem.getPhoneNumber();
}
@Override
public int getSpeakVolumn() {
return modem.getSpeakVolumn();
}
}
测试类
package com.eric.designmodel.decorator;
import junit.framework.TestCase;
/**
*
* **/
public class MainTest extends TestCase {
private static final String NUMBER = "10";
public void testNoneDecotarot() {
Modem modem = new HayesModem();
modem.dial(NUMBER);
assertTrue(modem.getSpeakVolumn() == 0);
assertTrue(modem.getPhoneNumber().equals(NUMBER));
}
/**
* 被Loud装饰器装饰过的Modem对象,在调用dial方法时, volumn会被设置为10
*/
public void testLoudDecotarot() {
Modem modem = new HayesModem();
LoudDecorator loudModem = new LoudDecorator(modem);
loudModem.dial(NUMBER);
assertTrue(loudModem.getSpeakVolumn() == 10);
assertTrue(loudModem.getPhoneNumber().equals(NUMBER));
}
/**
* 被Screen装饰器装饰过的Modem对象,在调用dial方法时,number会加上区号
*/
public void testScreenDecotarot() {
Modem modem = new HayesModem();
ScreenDecorator loudModem = new ScreenDecorator(modem);
loudModem.dial(NUMBER);
assertTrue(loudModem.getSpeakVolumn() == 0);
assertTrue(loudModem.getPhoneNumber().startsWith(ScreenDecorator.NUMBER_PREFIX));
}
/**
* 被Screen以及Loud装饰器装饰过的Modem对象,在调用dial方法时,number会加上区号并且volumn会被设置为10
*/
public void testDoubleDecotarot() {
Modem modem = new HayesModem();
Modem screenModem = new ScreenDecorator(modem);
Modem loudModem = new LoudDecorator(screenModem);
loudModem.dial(NUMBER);
assertTrue(loudModem.getSpeakVolumn() == 10);
assertTrue(loudModem.getPhoneNumber().startsWith(ScreenDecorator.NUMBER_PREFIX));
}
}
优点
将每个特性都作为一个单独的装饰类,在需求发生变化的时候对特性进行动态的组合.且不需要修改具体的子类.
个人觉得该模式对比Proxy模式而言强大之处在于可以把多个装饰效果应用到某个子类中.
应用
Decorator模式java的stand lib中也被广泛的应用,例如: Java中的IO是明显的装饰器模式的运用。FilterInputStream,FilterOutputStream,FilterRead,FilterWriter分别为具体装饰器的父类,相当于Decorator类,它们分别实现了InputStream,OutputStream,Reader,Writer类(这些类相当于Component,是其他组件类的父类,也是Decorator类的父类)。继承自InputStream,OutputStream,Reader,Writer这四个类的其他类是具体的组件类,每个都有相应的功能,相当于ConcreteComponent类。而继承自FilterInputStream,FilterOutputStream,FilterRead,FilterWriter这四个类的其他类就是具体的装饰器对象类,即ConcreteDecorator类。通过这些装饰器类,可以给我们提供更加具体的有用的功能。如FileInputStream是InputStream的一个子类,从文件中读取数据流,BufferedInputStream是继承自FilterInputStream的具体的装饰器类,该类提供一个内存的缓冲区类保存输入流中的数据。我们使用如下的代码来使用BufferedInputStream装饰FileInputStream,就可以提供一个内存缓冲区来保存从文件中读取的输入流。
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); //其中file为某个具体文件的File或者FileDescription对象