HeadFirst 设计模式 - Chapter 3 装饰者

2103 查看

设计原则:
类应该对扩展开放,对修改关闭

装饰者设计模式

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

主要思想

为已有功能动态地添加更多功能的一种方式。

从代码实现的角度来说,就是希望在不修改任何底层代码的情况下为对象赋予新的功能。当系统需要添加新功能的时候,通过新的代码来装饰原有类的核心职责或主要行为。这些新加入的代码相当于对原有核心代码的修饰,只在特定的应用场景下出现。

装饰者模式通过组合的方式来扩展对象的行为,而不依赖于继承,也就是说虽然类的框架中包含继承,但只是为了获取正确的类型,而不是继承一种行为。行为来自于装饰者和基础组件,或者与其他装饰者之间的组合关系。

  • 装饰者和被装饰对象有相同的超类型。
  • 你可以用一个或多个装饰者包装一个对象。
  • 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
  • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象

类图如下:

Component类是基本的被装饰对象抽象类,定义了一些功能接口,ConcreteComponent是一个实际的功能类实现。Decorator则是需要为原始核心功能类动态添加功能时的装饰类。ConcreteDecorator是装饰类的实现,包含了一个封装的Component对象(实例变量),每个装饰者功能独立,只负责对自己包含的对象进行自身功能添加,不关注其他功能。装饰的时候考虑先后顺序(如果需要的话)。装饰者还可以添加新的行为,定义新的状态,新的行为是在旧的行为前面或者后面添加一个计算来实现,新结果的获取将依赖或者委托给旧行为的执行结果。

一个装饰者模式的实际应用是: java io里面的InputStream提供基本的按照流读入一个流,而FileInputStream,StringBufferInputStream,ByteArrayInputStream分别作为几种不同类型的流读取方式。而针对FileInputStream又包含诸如BufferedInputStream,PushBackInputStream这些装饰者为文件输入流提供新的功能,处理不同的应用需求。

缺陷

装饰者模式的劣势在于如果客户程序依赖于组件的某种特殊类型,由于装饰是透明的,将出现问题。装饰者模式会导致设计过程中出现许多小对象(每次装饰都需要对象作为新的组件),过渡使用程序会变得复杂。比如在实例化组件的过程中,不仅需要实例化组件,还需要把组件包装进装饰者中,这个问题可以结合工厂模式和生成器模式解决。

代码

相当于抽象的Compoent类

javapublic abstract class Beverage {
    protected String description = "Unknown Beverage";
    public String getDescription(){
        return description;
    }
    public abstract float cost();
}

四个具体组件,每个代表一种咖啡类型

javapublic class DarkRoast extends Beverage {
    public DarkRoast(){
        description = "Most Excellent Dark Roast";
    }
    public float cost(){
        return 1.05f;
    }
}

抽象的Decorator类

javapublic abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

具体的装饰者类,每个代表一种扩展功能:调料装饰者

java// specific decorator
public class Mocha extends CondimentDecorator {
    Beverage beverage;
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Mocha";
    }

    @Override
    public float cost() {
        // TODO Auto-generated method stub
        return .20f+beverage.cost();
    }
}

测试类

javapublic class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        // A cup of espresso without any condiment
        System.out.println(beverage.getDescription()+"$"+beverage.cost());
        // A cup of darkroast with condiment 
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2); // decorate with mocha
        beverage2 = new Mocha(beverage2);// decorate with mocha again
        beverage2 = new Whip(beverage2); // decorate with whip

        System.out.println(beverage2.getDescription()+"$"+beverage2.cost());
        // A cup of houseblend with condiment
        Beverage beverage3 = new HouseBlend();
        beverage3 = new Soy(beverage3); // decorate with mocha
        beverage3 = new Mocha(beverage3);// decorate with mocha again
        beverage3 = new Whip(beverage3); // decorate with whip
        System.out.println(beverage3.getDescription()+"$"+beverage3.cost());
    }
}