装饰者模式的作用主要在于控制类的数量。其核心思想是利用组合来代替继承,从而动态地扩展类的功能。
后者更好地控制了类的数量,假设需要扩展 n 个功能,那么继承所需的类的数量为 n! 数量级
而采用装饰着模式则为 O(n)
先给出以下结论,带着为什么来思考:
- 装饰者和被装饰对象有相同的超类型(接口或者类都ok)
- 可以使用一个或者多个装饰者包装一个对象
- 既然装饰者和被装饰者具有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它
- 装饰者可以在所委托被装饰者的行为之前/或之后,加上自己的行为,以达到特定的目的
- 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢地装饰者来装饰对象
如何装饰者模式:
- 把类的核心职责和装饰功能区分开来
- 装饰者可以添加新的方法,但是能继续被装饰的方法只能是共同超类中的接口方法
实例探究Decorator
Head First Design Pattern 中谈到一个非常适合使用装饰者模式的案例,就是饮料店的案例,我们先来看一下需求:
有一家新开的饮料店,现在我们需要为订单系统设计相应的类,以使得我们可以计算相应的价格。
我们可以这样设计
1 | public interface Beverage{ |
2 | double cost(); |
3 | } |
4 | |
5 | // 基础类 |
6 | public class BaseBeverage impelments Beverage{ |
7 | |
8 | public double cost(){ |
9 | return 10; |
10 | } |
11 | } |
12 | |
13 | //装饰类 |
14 | public class Decorator implements Beverage{ |
15 | private Beverage bev; |
16 | |
17 | public Decorator(Beverage bev){ |
18 | this.bev = bev; |
19 | } |
20 | |
21 | public double cost(){ |
22 | return super.cost(); |
23 | } |
24 | |
25 | } |
26 | |
27 | //具体装饰类 |
28 | ------------------------------------------- |
29 | public Moca extends Decorator{ |
30 | |
31 | |
32 | public double cost(){ |
33 | return super.cost()+5; |
34 | } |
35 | } |
36 | |
37 | public Kapc extends Decorator{ |
38 | |
39 | public double cost(){ |
40 | return super.cost()+4.5; |
41 | } |
42 | } |
43 | ..... |
综合上面的,我们可以发现严格的装饰者模式遵循下面的类结构
注: Concreat意味实际的,完全的,表示是一个实体类,而非抽象类或者接口
装饰者模式的一些变化
1.装饰者模式的简化
大多数情况下,装饰者模式的实现都要比上面给出的例子要简单
可以考虑去掉抽象的Component类(接口),把 Decorator 作为一个ConcreatComponent子类,如下图:
还可以将Decorator和ConcreteDecorator类的责任合并成一个类
2.透明性的要求
装饰者模式对客户端的透明性,要求程序不要声明一个ConcreteComponet类型的变量,而应当声明一个Component类型的变量。用饮料的例子来说,不要声明一个 BaseBeverage或者更具体的Decorator类,而应当声明一个Beverage类型。
1 | Beverage bev = new Basebevrage(); |
2 | Beverage moca = new Moca(bev); |
而不是
1 | Moca moca = new Moca(bev); |
半透明的装饰者模式
然而,纯粹的装饰者模式不常见,装饰者模式的用意在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。比如说在饮料里,Moca咖啡可以赠送一张电影票。
1 | public Moca extends Decorator{ |
2 | |
3 | public double cost(){ |
4 | return super.cost()+5; |
5 | } |
6 | |
7 | // 重点关注这个新增的功能 |
8 | public String getMovieCode(){ |
9 | String moviewCode = ... |
10 | return movieCode; |
11 | } |
12 | } |
允许装饰模式改变接口,增加新的方法,这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:
1 | Moca moca = new Moca(new Beverage()); |
2 | System.out.println(moca.getMovieCode()); |
半透明的装饰者模式也称为半装饰,它介于装饰者模式和适配器模式之间,也称为半适配器模式。
装饰者模式的优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性,继承是静态的,而装饰则是动态的
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合
装饰模式的缺点:
由于使用装饰模式,会产生比继承关系更多的对象。更多的对象使得查错更为困难,特别是这些对象看上去都很像