装饰模式(Decorator)
思想概要
装饰模式是对里氏替换的一种扩充,里式替换原则,即:基类可以出现的地方都可以替换为子类。这似乎就是”继承“的标准定义嘛,但是我们的前辈常常告诫我们,继承是危险的,要谨慎使用继承,除非你能够证明A is B,而不是A like B。对于所有A like B的地方,都要积极的使用组合,而不是继承。继承之所以不受待见,是因为它破坏了封闭原则。子类不仅继承了基类的public函数,同时它也可以访问基类的protected函数,而这些函数本质上是包封闭的,开发者们不会对封闭函数的一致性负责,一旦基类在今后的某次升级中被改变,那子类就很有可能不能正常运行。既然不推荐使用继承,那对已有类的功能加强就落在了以组合为基本架构的装饰模式上。组合的好处是你无法访问public以外的函数,可以充分享受public函数拥有的一致性福利,不用担心随着版本的不同而功能失效。
依旧是盗图,请原谅。上图展示了装饰者模式的UML结构,真实类为ConcreteComponent,基础装饰者为Decorator,类似于代理模式,装饰者也是要调用真实类的API来实现功能。但是就像我们之前所说的一样,装饰者不是真实类的子类,它不通过继承来扩展功能,而是组合了真实类,同时,为了实现外部接口的一致性,它实现了Componet接口,外部代码可以像使用真实类一样使用装饰者。
不同于代理模式,代理模式的使用者们不太在意代理类做了哪些事,因为代理者本身不扩展核心功能,而使用装饰者模式的用户更多的是强调装饰者们扩展了哪些功能,他们更在意装饰者的存在。所以在类图里你可以看到,除了基本装饰者(Decorator)外,还有继承自基础装饰者的ConcreteDecoratorA和ConcreteDecoratorB,因为它们各自扩展不同的功能,所以会分化出各种类,而在代理模式中,你几乎不会看到代理者还有子类。
讲到装饰者模式的具体事例,永远逃不掉加糖加奶咖啡的例子,很无聊但是很有代表性,你值得看看。
咖啡可以加糖也可以不加糖,可以加奶也可以不加奶,当然也可以既加糖又加奶。这个问题的着眼点其实已经不是咖啡了,它不再重要,重要的是糖和奶这两个装饰属性。这就是上面提到的,使用装饰者的客户往往不在乎真实类的功能,他们更在乎装饰者扩展了哪些内容。我们通过具体代码来看看它们是如何工作的:
public interface ICafe { /* API */ void getCafe(); }
这是咖啡接口,只有”要咖啡“这个基本API。
public class ConcreteCafe implements ICafe { @Override public void getCafe() { System.out.println("Make cafe"); } }
这是原味咖啡制作类,仅仅做一杯黑咖啡
public class ConcreteDecorator implements ICafe { private final ICafe cafe; public ConcreteDecorator(ICafe ref) { cafe = ref; } @Override public void getCafe() { cafe.getCafe(); } }
装饰者基类,没有追加任何功能,仅仅调用接口类的共有函数。
public class SugarDecorator extends ConcreteDecorator { public SugarDecorator(ICafe ref) { super(ref); } @Override public void getCafe() { super.getCafe(); System.out.println("Add sugar"); } }
加糖咖啡类,仅仅是在做咖啡之后加了一块糖
public class MilkDecorator extends ConcreteDecorator { public MilkDecorator(ICafe ref) { super(ref); } @Override public void getCafe() { super.getCafe(); System.out.println("Add milk"); } }
加奶咖啡,仅仅是在做咖啡后加了点奶
public class Launcher { public static void main(String[] args) { ICafe cafe = new MilkDecorator(new SugarDecorator(new ConcreteCafe())); cafe.getCafe(); } }
检验结果的客户代码。它要了杯加糖又加奶的咖啡,运行结果是:
Make cafe
Add sugar
Add milk
总体来说,装饰类比较好掌握,不断的对基础类的装饰可以得到复杂的功能,这比修改实际代码更快或更好。