装饰者模式
现实案例
- 现在有一个咖啡店,他们有很多很多不同种类的咖啡/饮料在售卖;
- 顾客购买咖啡时,可以要求向里面加入各种调料(奶泡,牛奶,砂糖,等...)。根据顾客加的东西,会在咖啡的基础上进行额外收费;
- 现在咖啡店需要一套 “订单系统” 可以让他们很轻松的计算出每个咖啡的价格;
实现一:❌ 用继承来实现:
- 先创建一个 Beverage 抽象类,店里所有的饮料都要继承自此类;
- 每种调料(牛奶,砂糖,等...)都各自有一个 int 类型实例属性,它们的属性值代表对应的调料点了多少份;
- 同时,每种调料的实例属性,还有对应的 getter / setter 方法;
- Beverage 里面有个方法
cost
,它会计算出咖啡的价格:
- 我们默认,调料价钱计算方法是通用的;
- 现在 Beverage 的
cost
方法里算出调料的价格; - 在每个饮料子类中,我们需要重写覆盖这个
cost
方法。在子类的 cost
方法中,先调用父类的 cost
算出调料的价格,然后再加上对应饮料的价格;

实现一的问题:
- 无论添加,还是删除调料,我们都要改变 Beverage 抽象类的
cost
方法; - 我们假定所有饮料都用同一套调料计算方法,但是如果需求不是这样的呢?我们就要加很多判断语句,或者在子类重写一套计算方法;
- 并不是每种饮料都可以加所有的调料。比如 “茶” 可能并不需要加奶泡,但是上面的实现,让每种饮料都继承了所有 调料的 setter / getter 方法;
什么是装饰者模式
- 装饰者模式遵循了 “开放-关闭原则”。对扩展开放,就修改关闭。也就是在不修改原有代码的情况下,给类添加新的行为;
- 装饰者模式动态的将新行为附加到原本的对象上,以此来扩展功能;
- 相对于继承,装饰者提供了更有弹性的替代方案;
装饰者模式的实现
- 在之前,我们的模式是,一个超类下面有很多个子类。假设这个超类叫 Component,子类叫做 ConcreteComponent;
- 现在在这个超类下,我们再创建一个装饰超类/接口;
- 然后再在装饰超类/接口下,实现装饰者类:
- 装饰者内部有一个 Component 类型实例属性,它可以保存继承自 Component 的子类实例的引用;这里我们把它称作 “被装饰者”;
- 在实例化装饰类的时候,我们把 Component 的子类实例传进构造函数,并保存到刚刚的实例属性中;
- 通过对被装饰者的引用,我们可以进行各种操作,给它加上各种行为;
- 因为装饰者和被装饰者有相同的超类型,所以我们把装饰者看作是对被装饰者的 “包装”。装饰者可以替代被装饰者对象;
- 通过上面这种方式,我们可以在如何时候,动态地,不限量地来给目标对象加上装饰对象;
- 并以此来扩展目标对象的行为;

实现二:✅ 用装饰者模式实现
- 通过装饰者模式,我们在运行时用调料来 “装饰” 饮料;
- 🌰 以一杯 “摩卡奶泡深焙咖啡” 举例:
- 我们先有一个 Beverage 超类。它里面有一个
cost
抽象方法; - 然后创建一个深焙咖啡 DarkRoast 类,让它继承自 Beverage 类。它要实现自己的
cost
方法,里面返回单个咖啡的价格; - 创建 Condiment 调料抽象类,它继承自 Beverage 类。作为装饰者的超类,所有的调料装饰者类都继承自它;
- 创建摩卡 Mocha 装饰类;
- 创建奶泡 Whip 装饰类;
- 两个装饰类都包含一个 Beverage 类型的实例属性,它作为 “被装饰者” 的引用;
- 装饰类里面要实现
cost
方法,它把 “当前调料的价格” 加到 “被装饰对象的价格” 之上,并将结果返回出去;
- 现在,先创建一个 DarkRoast 实例;
- 然后创建 Mocha 装饰实例,把 DarkRoast 实例作为 “被装饰者” 传进去;
- 然后创建 Whip 装饰实例,把前一个 Mocha 实例作为 “被装饰者” 传进去;
- 现在调用 Whip 实例的
cost
方法就会返回正确的价格;

总结:装饰者模式
- 继承属于扩展形式之一,但不一定是可以达到弹性设计的最佳方案;
- 在设计时,应该允许对象的行为被扩展,而并不需要改变现有代码;
- 通过组合和委托,可以动态地给对象加上新的行为;
- 装饰者和被装饰者类都继承自同一个超类;他们的实例之间可以互相替换;
- 通过这个特性,装饰者模式得以实现,装饰者可以在被装饰者身上加上新的行为,然后再用自己替代掉被装饰者。通过这种方式,被装饰者的功能得以扩展;
- 虽然装饰者模式很灵活好用,但也要注意:
- 装饰者类把所有的被装饰者都看作 “同一个超类型” 的实例。它并不分辨被装饰者的具体子类型。这使得,如果一个行为只针对特别的一个子类型生效,那装饰者就需要再内部做类型判断。可能这种情况下,装饰者模式就不好用了;
- 装饰者模式会让设计中出现很多个小对象(装饰者类)。如果过度使用,也不宜与维护,也会让程序变得复杂;