设计模式之装饰者模式

装饰者模式(head first 设计模式)

定义

装饰着模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

体现的OO原则: 对扩展开放,对修改关闭。

特点

  1. 装饰者和被装饰对象具有相同的超类型
  2. 可以用一个或多个装饰者包装一个对象
  3. 由于装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,都可以用装饰过的对象代替她
  4. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的
  5. 对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的用需要的装饰者来装饰对象

类图

思考:为什么Decorate类扩展自Component类?

  1. 装饰者和被装饰者必须是一样的类型,我们在此使用继承达到“类型匹配”
  2. 类型匹配意味着装饰者和被装饰者具有相同的接口,从而装饰者可以取代被装饰者
  3. 新的行为并不是继承自超类,而是由组合对象得到,即所有饮料和调料可以更有弹性的加以混合和匹配
  4. 我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,必须修改代码
  5. Component类型可以使用抽象类,也可以使用接口

星巴兹咖啡销售系统装饰者模式类图关系:

例子

  1. Beverage 组件类

    /**
    * Beverage(饮料类)相当于抽象的 Component 类
    */
    public abstract class Beverage {
    
     String desc = "Unknown Beverage";
    
     public String getDesc(){
         return desc;
     }
    
     public abstract double cost();
    }
    
  2. 四个具体组件

/**
 * Espresso 浓缩咖啡
 * 1.让 Espresso扩展自 Beverage 类
 * 2.设置饮料的描述
 * 3.实现 cost 方法,计算 Espresso 的价钱,先不管调料的价钱
 */
public class Espresso extends Beverage {

    public Espresso(){
        desc = "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

HouseBlend、DarkRoast、Decat 类与 Espresso 类 类似。

  1. 装饰者共同实现的接口(抽象类)
/**
 * Condiment(调料)抽象类,装饰者类
 * 1.首先,必须让 CondimentDecorator 能够取代 Beverage 类,所以扩展自 Beverage 类
 * 2.所有的调料装饰者都必须重新实现 getDesc()方法
 */
public abstract class CondimentDecorator extends Beverage{
    public abstract String getDesc();
}
  1. 装饰者
/**
 * 1.摩卡是一个装饰者,所以让它继承 CondimentDecorator,而 CondimentDecorator 继承 Beverage
 * 2.要染 Mocha 能够引用一个 Beverage,做法:a.用一个实例变量记录饮料,也就是被装饰着
 *   b.构造函数传入beverage
 * 3.更改描述和价格
 */
public class Mocha extends CondimentDecorator{

    Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDesc() {
        return beverage.getDesc() + ", Mocha";
    }

    @Override
    public double cost() {
        return 0.20 + beverage.cost();
    }
}

Soy、Whip类也与 Mocha 类似。

5.测试

private void testDecorator() {
        Beverage beverage = new Espresso();
        Log.d(TAG, beverage.getDesc() + "$" + beverage.cost());

        Beverage beverage1 = new DarkRoast();
        beverage1 = new Mocha(beverage1);
        beverage1 = new Mocha(beverage1);
        beverage1 = new Whip(beverage1);
        Log.d(TAG, beverage1.getDesc() + "$" + beverage1.cost());

        Beverage beverage2 = new HouseBlend();
        beverage2 = new Soy(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        Log.d(TAG, beverage2.getDesc() + "$" + beverage2.cost());
    }

结果:

D/MainActivity: Espresso$1.99
D/MainActivity: DarkRoast, Mocha, Mocha, Whip$1.44
D/MainActivity: House Blend Coffee, Soy, Mocha, Whip$1.3399999999999999

java I/0 中的装饰者

LineNumberInputStream( BufferedInputStream( FileInputStream ) )

  1. LineNumberInputStream 是一个具体的装饰者,它加上了计算行数的能力。
  2. BufferedInputStream 是一个具体的装饰者,它加入两种行为:缓冲输入、readline()方法
  3. FileInputStream 是被装饰的组件。 I/O库提供了几个组件,包括:FileInputStream、StringBufferInputStream、ByteArrayInputStraam…

自定义自己的 I/O 装饰者

读取文件,大写转小写:

/**
 * 扩展 FilterInputStream 类,这是所有 InputStream 的抽象装饰者
 */
public class LowerCaseInputStream extends FilterInputStream {

    public LowerCaseInputStream(InputStream in) {
        super(in);
    }

    /**
     * 针对字节
     * @return
     * @throws IOException
     */
    public int read() throws IOException{
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }

    /**
     * 针对字节数组
     * @param b
     * @param offset
     * @param len
     * @return
     * @throws IOException
     */
    public int read(byte[] b, int offset, int len) throws IOException{
        int result = super.read(b, offset, len);
        for (int i = offset; i < offset + result; i++){
            b[i] = (byte) Character.toLowerCase((char)b[i]);
        }
        return result;
    }
}

使用:

int c;
try {
    //得到资源中的Raw数据流
    InputStream in = getResources().openRawResource(R.raw.test);
    in = new LowerCaseInputStream(in);
    while ((c = in.read()) >= 0){
        Log.d(TAG, (char)c + "");
        //System.out.print((char)c);
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

总结

  • 在我们的代码中,应该允许行为遵循对扩展开放-对修改关闭的原则,这样就可以无需修改现有的代码就可以实现我们扩展的功能。

  • 装饰者模式意味着一群装饰者类,这些类用来包装具体组件。

  • 装饰者反映出被装饰者的组件类型(具有相同的类型)

  • 装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将被装饰者的行为整个取代,而达到特定的目的。

  • 可以用无数个装饰者包装一个组件。

  • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。

  • 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得复杂。