• 设计模式之: 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对象  


  • 相关阅读:
    Java Runtime.exec()的使用
    加密备忘
    maven 配置 Java Servlet API
    flume spooldir bug修复
    修复eclipse build-helper-maven-plugin 异常
    Win10系统安装Office2016错误,提示需要更新客户端的解决方法
    ORA-14300: 分区关键字映射到超出允许的最大分区数的分区
    ORA-14402:更新分区关键字列将导致分区更改(分区表注意)
    oracle 11g自动时间分区备忘
    Oracle计算时间函数(numtodsinterval、numtoyminterval)
  • 原文地址:https://www.cnblogs.com/bbsno1/p/3275568.html
Copyright © 2020-2023  润新知