设计模式之装饰者模式
背景
以去咖啡馆点咖啡为例子,假设一个咖啡馆可以提供Espresso、HouseBlend、Decaf、DarkRoast这四种咖啡的类型,同时对于这每种咖啡还提供Milk、Mocha、Soy、Whip这四种调料,即每种咖啡都可以组合这失踪调味中的任意几种。
假设给这个咖啡馆设计一个点餐系统,根据用户的请求,得出最后的价格和咖啡的各种配料信息。首先根据我们直观的思路就是,根据用户的请求组合出相应的订单信息以及最后价格信息,其大概的类图可以看做下面这样:
注意:上面我还只是添加了很小的一部分,想象一下,咖啡类型的数量和调料的数量进行组合,简直就是类爆炸,可以看出来,这样的设计师非常不合理的,除去扩展性很差之外,当咖啡种类和调料种类数量在不断增加的时候,最后类的数量简直就是爆炸式增长。
那么有没有更好一点的设计方案呢?
另一种思路:
假设在Beverage类中为每一个调料设计一个控制器,用来控制是否加入该调料,然后在getCost方法中根据判断哪些调料加入了,从而获取相应的价钱,最后得到总价钱。其类图大概的样子如下所示:
说明:通过相应的调料控制器来控制是否加入了该调料,然后获取相应的调料的价格然后累加算作最后的总价格返回给客户。
这似乎看上去可行,不错!确实可行,但是却违背了之前我们讲到的几个设计原则。
首先,每当调料的价格发生了变化的时候(而且我认为这似乎是很常见的情况),我们都需要修改代码。
然后,如果咖啡店新增加了一个新的咖啡品种或者新的咖啡调料,哪的修改超类Beverage中的代码,还需要更改getCost()方法。
接着,假设新出现了一个新的饮料A,在这个新的饮料A中是不会出现调料milk,那么对于饮料A来说调料milk是不需要的,而且是浪费的,以此类推,假设饮品的种类越来越多,在超类Beverage中添加的控制器也会越来越多,但是对于每一类饮品来说,可用的或者需要用到的调料控制器仅仅是其中的几个,这样的话就会造成很多的冗余和资源浪费。
还有,继续思考,我们知道,假如加入了milk,这milk的控制器就会为true,表示当前咖啡中加入了milk,好,这没问题!但是如果我想点一份加了两份milk的咖啡了?如何去表示两份?在计算价格的时候似乎也是一件麻烦事,因为有可能加了三份、四份。。。。
思考:类比Angular中的管道,对于每一种调料,每当需要这种调料的时候,就调用一下这个调料,需要几个就调用几个,需要不同种类的调料就调用不同种类的调料。
由此得到了一个新的设计原则:
类应该对扩展开放,对修改关闭—- 开放-关闭原则
装饰者模式
认识装饰者模式
接着上面的背景,然后根据之前的分析思路:
首先,点一杯Espresso咖啡
然后,我需要加点Milk,于是用Milk来装饰它,此时就是Espresso+Milk
接着,我还想加点Whip,于是再用Whip来装饰它,即Espresso+Milk+Whip
继续,我发现我好想还想再来点Milk,继续使用Milk装饰,则:Espresso+Milk+Whip+Milk
最后,需要结账,调用getCost方法返回最后的价钱。
其中最值得注意的就是计算价格上,是通过什么方式委托每一种调料计算价格的呢?
计算模型大概如下所示:
装饰者模式定义
装饰者模式 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
通过上面的分析,我们现在对装饰者模式的了解大概如下:
1、装饰者和被装饰者应该是继承自同一个超类,即两者有相同的类型。因为被装饰者在被装饰之后任然属于被装饰者,这就要求两者的类型应该统一。
2、一个对象可以使用一个或者多个装饰者来进行装饰,而且是不限制数量的,并且可以在程序运行的任何时间。
3、装饰者可以在对某个对象进行装饰之前或者之后进行一些自己的相关操作,从而达到一些特定场景的需求。
根据上面对装饰者模式的了解,可以得到如下的简要的类图:
说明:
1、每个装饰者中都