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

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


原文地址:https://www.cnblogs.com/bbsno1/p/3275568.html