装饰者模式

装饰者模式

  • by Head First 设计模式:

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

  • by Dive into Design Patterns:

    Also known as Wrapper

    Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

设计原则 #

  1. 找出应用之中可以变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  2. 针对接口编程,而不是针对实现编程
  3. 多用组合,少用继承
  4. 为交互对象的松耦合设计而努力
  5. 对象应对拓展开放,而对修改关闭(开闭原则)
    • 听起来很矛盾,但是确实有一些OO技巧,允许系统在不修改代码的情况下,进行功能拓展(想想观察者模式)。
    • 装饰者模式也是一个好例子,完全遵循开放-关闭原则
    • 通常,设计无法完全遵循开放-关闭原则。也没有必要让所有的代码都强行按照这个原则去设计,强行如此做 只会增加工作量,并且让代码更加复杂。

UML简图 #

classDiagram
class Component {
  << Abstract >>
  +methodA()
  +methodB()
}

Component <|-- ConcreteComponent
class ConcreteComponent {
  +methodA()
  +methodB()
}

Component <|..* Decorator
class Decorator {
  << Abastract >>
  #Component component
  +methodA()
  +methodB()
}

Decorator <|-- DecoratorA
class DecoratorA {
  #Component component
  +methodA()
  +methodB()
}

Decorator <|-- DecoratorB
class DecoratorB {
  #Component component
  +methodA()
  +methodB()
}

Decorator <|-- DecoratorC
class DecoratorC {
  #Component component
  -Object newStats
  +methodA()
  +methodB()
}

示例代码 #

装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有类的一个"包装"。

重点在于“包装”两个字。字面理解,即新类包括了旧类。我们常常见到的java.io包里面的形如

new BufferedReader(new InputStreamReader(new FileInputStream("foo")))

就是这种模式的典型表现。

在本示例中,我们开了一个咖啡馆,有几种不同口味的咖啡(深焙,浓缩,混合),另外每种咖啡还可以选择不同的调料以增加风味。我们可以使用装饰者模式来实现。

首先,创建一个Beverage接口和几个实现了Beverage接口的具体咖啡类:

被装饰者 #

 1public abstract class Beverage {
 2    String description = "Unknown Beverage";
 3
 4    Size size = Size.MEDIUM;
 5
 6    double price = 0.0d;
 7
 8    public String getDescription() {
 9        return description;
10    }
11
12    public abstract double cost();
13
14}
15
16public class DarkRost extends Beverage {
17
18    public DarkRost() {
19        this.price = 3.99;
20        this.description = "DarkRoast";
21    }
22
23    public DarkRost(Size size){
24        this.size = size;
25        this.description = "DarkRoast";
26        switch (size){
27            case MEDIUM: this.price = 3.99; break;
28            case LARGE: this.price = 4.99; break;
29            case EXTRA_LARGE: this.price = 5.99; break;
30            default:
31        }
32    }
33
34    @Override
35    public String getDescription() {
36         return this.size.name() + " " + this.description;
37    }
38
39    @Override
40    public double cost() {
41        return price;
42    }
43}
44
45public class HouseBlend extends Beverage {
46
47    public HouseBlend(Size size) {
48        this.size = size;
49        this.description = "House-Blend";
50        switch (size){
51            case MEDIUM: this.price = 4.99; break;
52            case LARGE: this.price = 5.99; break;
53            case EXTRA_LARGE: this.price = 6.99; break;
54            default:
55        }
56    }
57
58    public HouseBlend() {
59        this.price = 4.99;
60        this.description = "House-Blend";
61    }
62
63    @Override
64    public String getDescription() {
65        return this.size.name() + " " + this.description;
66    }
67
68    @Override
69    public double cost() {
70        return price;
71    }
72}

额外地,我们使用一个枚举类来标识咖啡的规格,最小是中杯~

1public enum Size {
2    // 中杯 大杯 超大杯
3    MEDIUM, LARGE, EXTRA_LARGE
4}

装饰者 #

接下来,创建装饰者抽象类和具体的装饰者,它实现了Beverage接口,用来拓展咖啡的风味:

  1public abstract class Condiment extends Beverage {
  2
  3    protected Beverage beverage;
  4
  5}
  6
  7public class Milk extends Condiment {
  8
  9    public Milk(Beverage beverage) {
 10        this.beverage = beverage;
 11        this.size = beverage.size;
 12        switch (beverage.size) {
 13            case MEDIUM:
 14                price = 0.99;
 15                break;
 16            case LARGE:
 17                price = 1.49;
 18                break;
 19            case EXTRA_LARGE:
 20                price = 1.99;
 21                break;
 22            default:
 23        }
 24    }
 25
 26    @Override
 27    public String getDescription() {
 28        return beverage.getDescription() + " Milk";
 29    }
 30
 31    @Override
 32    public double cost() {
 33        //
 34        return beverage.cost() + price;
 35    }
 36}
 37
 38public class Mocha extends Condiment {
 39
 40    public Mocha(Beverage beverage) {
 41        this.beverage = beverage;
 42        this.size = beverage.size;
 43        switch (beverage.size) {
 44            case MEDIUM:
 45                price = 1.09;
 46                break;
 47            case LARGE:
 48                price = 1.69;
 49                break;
 50            case EXTRA_LARGE:
 51                price = 2.29;
 52                break;
 53            default:
 54        }
 55    }
 56
 57    @Override
 58    public String getDescription() {
 59        return beverage.getDescription() + " Mocha";
 60    }
 61
 62    @Override
 63    public double cost() {
 64        double v = beverage.cost() + price;
 65        return BigDecimal.valueOf(v).round(MathContext.DECIMAL32).doubleValue();
 66    }
 67}
 68
 69public class Soy extends Condiment {
 70
 71    public Soy(Beverage beverage) {
 72        this.beverage = beverage;
 73        this.size = beverage.size;
 74        switch (beverage.size) {
 75            case MEDIUM:
 76                price = 0.49;
 77                break;
 78            case LARGE:
 79                price = 0.99;
 80                break;
 81            case EXTRA_LARGE:
 82                price = 1.29;
 83                break;
 84            default:
 85        }
 86    }
 87
 88    @Override
 89    public String getDescription() {
 90        return beverage.getDescription() + " Soy";
 91    }
 92
 93    @Override
 94    public double cost() {
 95        return beverage.cost() + price;
 96    }
 97}
 98
 99public class Whip extends Condiment{
100
101    public Whip(Beverage beverage) {
102        this.beverage = beverage;
103        this.size = beverage.size;
104        switch (beverage.size) {
105            case MEDIUM:
106                price = 1.49;
107                break;
108            case LARGE:
109                price = 1.99;
110                break;
111            case EXTRA_LARGE:
112                price = 2.49;
113                break;
114            default:
115        }
116    }
117
118    @Override
119    public String getDescription() {
120        return beverage.getDescription() + " Whip";
121    }
122
123    @Override
124    public double cost() {
125        return beverage.cost() + price;
126    }
127}

测试类 #

 1public class TestDecoratorBeverage {
 2
 3    public static void main(String[] args) {
 4        // 双份摩卡加奶混合口味咖啡(超大杯)
 5        Beverage hb = new Mocha(new Mocha(new Milk(new HouseBlend(Size.EXTRA_LARGE))));
 6        // 中杯豆浆奶泡口味深烘焙(应该很难喝吧~)
 7        Beverage dr = new Whip(new Soy(new DarkRost(Size.MEDIUM)));
 8
 9        System.out.printf("%s: %2.2f\n", hb.getDescription(), hb.cost());
10        System.out.printf("%s: %2.2f", dr.getDescription(), dr.cost());
11    }
12}
13///:~
14//EXTRA_LARGE House-Blend Milk Mocha Mocha: 13.56
15//MEDIUM DarkRoast Soy Whip: 5.97
16//

小结 #

  1. 继承属于拓展的形式之一,但是不见得是达到弹性拓展的最佳方式。
  2. 在我们的设计中,应该允许行为可以被拓展,而无需修改现有的代码。
  3. 组合和委托可用于运行时动态地加上新行为。
  4. 除了继承,装饰者也可以让我们拓展行为。
  5. 装饰者模式意味着一群装饰者类,用来包装具体的组件
  6. 装饰者可以在被装饰者前面/后面加上自己的行为,或者替代被装饰者的行为,以达到目的。
  7. 如有必要,装饰者可以无限制地使用。
  8. 装饰者一般对组件的客户是透明的,除非客户程序依赖组件的具体类型。
  9. 装饰者模式会导致设计中多出许多小类,如果过度使用,会让程序变得复杂