您的位置:首页 > 其它

设计模式之: Decorator(装饰器)模式

2013-08-22 18:16 381 查看
在说明什么是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对象
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: