装饰者模式
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.
设计原则 #
- 找出应用之中可以变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 多用组合,少用继承
- 为交互对象的松耦合设计而努力
- 对象应对拓展开放,而对修改关闭(开闭原则)
- 听起来很矛盾,但是确实有一些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//
小结 #
- 继承属于拓展的形式之一,但是不见得是达到弹性拓展的最佳方式。
- 在我们的设计中,应该允许行为可以被拓展,而无需修改现有的代码。
- 组合和委托可用于运行时动态地加上新行为。
- 除了继承,装饰者也可以让我们拓展行为。
- 装饰者模式意味着一群装饰者类,用来包装具体的组件
- 装饰者可以在被装饰者前面/后面加上自己的行为,或者替代被装饰者的行为,以达到目的。
- 如有必要,装饰者可以无限制地使用。
- 装饰者一般对组件的客户是透明的,除非客户程序依赖组件的具体类型。
- 装饰者模式会导致设计中多出许多小类,如果过度使用,会让程序变得复杂。