学完装饰者模式之后发现代码实现很简单,但是有种不知道在什么场合适合应用的感觉。对于书上写的咖啡例子感觉有点强行使用,不过对于原理讲解来说很合适,暂且记录下来,后续还需要结合实际应用例子加深理解。
装饰者模式 动态的将责任附加到对象身上,若要扩展功能,装饰者模式提供了比继承更有弹性的替代方案。
通俗点讲就是在运行时将功能扩展到原来的对象上,但是不需要修改原对象的代码。这样的好处就是在设计时想不到的功能,或者在设计时没法预料的变化,可以在将来发生时再去扩展,同时由于只用扩展后的对象替换原来的对象(扩展后的对象跟原来对象是同一种类型),不直接修改原来的其他实现,降低了出bug的风险。听起来好像很厉害,但实际上要获得这样的灵活性,必须满足一些条件,这个后面再说。
在装饰者模式中,作者引出了一条设计原则
开放-关闭原则 类应该对扩展开放,对修改关闭。
这个设计原则的目标是设计的类容易扩展(开放),在不修改现有的代码的情况下(关闭),就可以搭配新的行为,达到新的功能。这样的设计具有弹性,可以更好的应对改变。
装饰者模式从一开始就设计容易扩展的类,在不修改代码的情况下搭配装饰者,在装饰者中加入新的行为,达到新的功能。
接下来我们用书上的例子来具体解释装饰者模式。
假设你开了一家咖啡店,提供美式咖啡,焦糖玛奇朵,摩卡等等种类。顾客点其中的一种,其实都是普通咖啡加各种料做成的,算价钱的时候就是把咖啡和各种料的价格加起来。如果咖啡和料都是一个个对象,那实现这个过程可能就变成了下面的样子(不太恰当。。)
1
2
3
4
5
6
public class Mocha{
public double cost(){
// 咖啡 + 料1 + 料2
return coffee.cost() + condiment1.cost() + condiment2.cost();
}
}
这样的实现针对具体实现编程,同时应该使用组合来实现,违背了设计原则。一旦配料发生改变,则需要修改具体的 Mocha 实现类。
如果使用装饰者模式来设计,则不存在这样的问题。
-
设计一个饮料的超类,所有咖啡都继承这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/** * 饮料抽象类,所有饮料的超类型 */ public abstract class Beverage { /** * @return 描述 */ public String getDescription(){ return "Unknown Beverage"; } /** * @return 价格 */ public abstract double cost(); }
-
再设计一个装饰者,调料都继承这个类型
1 2 3 4 5 6 7 8 9 10 11 12
/** * 所有调料装饰者的超类型 */ public abstract class CondimentDecorator extends Beverage { /** * 所有调料都必须重新实现描述方法 * @return 调料描述 */ public abstract String getDescription(); }
-
实现基础的咖啡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** * 综合咖啡,饮料的子类 */ public class Espresso extends Beverage { @Override public String getDescription() { return "咖啡"; } @Override public double cost() { return 1.99; } }
-
实现各种调料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
/** * 摩卡装饰者 */ public class Mocha extends CondimentDecorator { /** * 被摩卡封装的对象 */ private Beverage beverage; public Mocha(Beverage beverage){ this.beverage = beverage; } @Override public String getDescription() { return "摩卡" + beverage.getDescription(); } @Override public double cost() { return 0.55 + beverage.cost(); } } /** * 奶盖装饰者 */ public class Whip extends CondimentDecorator { private Beverage beverage; public Whip(Beverage beverage){ this.beverage = beverage; } @Override public String getDescription() { return "奶盖" + beverage.getDescription(); } @Override public double cost() { return 0.3 + beverage.cost(); } }
- 现在来制作一杯奶盖摩卡咖啡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/** * 制作奶盖摩卡咖啡,计算价格 * @param args */ public static void main(String[] args) { // 1.普通咖啡 Beverage espresso = new Espresso(); // 2.加料编程摩卡咖啡 Beverage mocha = new Mocha(espresso); // 3.加奶盖 mocha = new Whip(mocha); System.out.println(mocha.getDescription() + " : " + mocha.cost() + " 元"); }
如果需要不同类型的咖啡,只需要进行各种组合搭配就好了,这样方便进行扩展和改变。这个就是装饰者模式的具体实现了,可以看到装饰者模式就是定义一个个装饰者,跟被装饰者的类型一致,对被装饰者进行包装,然后在被装饰者的方法基础上增加功能,装饰者可以被无数次包装。
装饰者虽然为设计注入了弹性,但是也有一些缺点和要求
- 增加了设计的复杂度,会导致有大量的小类,让使用者一下不好理解
- 增加了实现的复杂度,在使用对应的类时,可能加很多层装饰者进行包装
- 在别人的代码上插入装饰者模式时,必须确保别人的代码没有针对具体实现类型的编码,比如 a
instanceof
A.class ,如果有,则装饰者替换原A类型后,可能会导致bug。
书上介绍可以结合工厂模式和生成器模式可以解决部分问题,这个可以后续再进一步了解。