在说明什么是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对象