学习笔记 ---- 设计模式之装饰者模式
StarBuzz原始案例:
这是StarBuzz的原始类图:
Beverage是一个抽象类,店内提供的饮料都必须继承自此类。
description实例变量用来对饮料进行描述,用getDescription()方法返回。
cost()是抽象方法,子类必须自己定义。
StarBuzz的需求:
购买咖啡时,可以加入各种不同的调味料,StarBuzz会根据调味料的不同收取不同的费用。
他们的第一个尝试:
很明显,类爆炸,为什么这样,我信,你懂的。。。
违反了我们已经学过的两个OO原则:
封装变化。
针对接口编程,而不针对实现编程。
他们的第二个尝试:
这是他们的类图:
利用实例变量和继承,来追踪饮料里添加了哪些调料。
cost()不再是抽象方法,在Beverage中,我们提供了cost()的实现,让他计算要加入各种饮料的调料价钱。子类仍将覆盖cost(),但是会调用超类的cost(),计算出基本饮料加上调料的价钱。
haser和setter方法来取得和设置调料的布尔值。
看着合理,实际上问题多多:
某种调料价格的改变我们得修改超类;
增加新调料又要修改超类;
开发出新饮料,对这些饮料而言(例如冰茶),某些调料可能并不适合,但是这个设计方案中,子类仍然将继承那些不适合的方法,例如haSoy(茶+豆浆)
顾客想要双倍Mocha,怎末办?
各种饮料增加了大中小杯,价格各不同,怎么办?
。。。
这种设计,于扩张,于维护,都有太多的问题。
对于继承的思考:
继承确实威力强大,但它并不是能够实现最有弹性和最好维护的设计。利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承相同的行为。
而利用组合和委托,一样可以实现继承的行为。然而利用组合的做法扩展对象的行为,可以在运行时动态的进行拓展。利用这个技巧,可以把多个新的行为,甚至是设计超类时没有想到的行为加到对象上,而且可以不用修改原来的代码。
通过动态的组合对象,可以写新的代码添加新的功能,而无需修改现有的代码。没有改变现有的代码,那么引入新的bug或产生以外副作用的机会就会大大减小。
总之,代码要免于改变,但又要支持扩展。
这其实就是很重要的一个设计原则,开放-关闭原则:
类应该对扩展开放,对修改关闭。
虽然有些矛盾,但是确实有一些技术,可以允许在不直接修改代码的情况下对其进行扩展。在选择需要被扩展部分的代码时要小心。每个地方都采用开放-关闭原则,是一种浪费,也没有必要,还会导致代码变得复杂难以理解。
认识装饰者模式:
通过例子和上图,我们可以知道:
1. 装饰着和被装饰者有相同的超类
2. 可以用一个或者多个装饰者包装一个对象
3. 因为装饰者和被装饰者有相同的超类,所以在任何需要原始超类的场合,可以用装饰过的对象代替它
4. 装饰者可以在所委托被装饰者的行为之前或者之后,加上自己的行为,已达到特定的目的。
5. 对象可以再任何时候被装饰,所以可以在运行时动态地,不限量的用你喜欢的装饰者来装饰对象。
由此,我们可以给装饰者模式下一个定义:
Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
装饰者模式动态地将职责附加到对象上。若要扩张功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式的类图:
Component抽象构件
接口或者抽象类,其定义最核心,最原始的对象,由被修饰的对象和装饰者继承或实现。在装饰者模式中,必然有一个最基本、最核心、最原始的接口或者抽象类充当Component抽象构件
ConcreteComponent具体构件
接口和抽象类的实现,定义被修饰的对象。
Decorator装饰角色
一般是个抽象类,实现接口或者抽象方法。里边必有一个private变量指向Component抽象构件。
ConCreteDecoratorA and ConcreteDecoratorB 具体装饰角色
装饰具体构件对象。
装饰者和被装饰者必须是同一类型,也就是有共同的超类或者实现共同的接口。这是相当关键的,因为装饰者能够被被装饰者取代。这里,如果是有共同的超类,继承的目的,是用来达到“类型匹配”,而不是利用继承获得行为。新的行为,来自装饰者和基础组件。
这样,基础组件可以被任意个装饰者,以任意的顺序装饰,也即任意顺序添加任意个行为。且可以不修改原来的代码。如果类的行为来自继承的话,那么类的行为只能在编译时动态决定,且如果有修改的话,则要修改原来的代码。
总结装饰者模式的优缺点以及要点
装饰者模式的优点
装饰者模式和被装饰者模式可以独立发展,而不会相互耦合。因为Component类无须知道Decorator类,Decorator类是从外部扩展Component类的功能,而Decorator类也不需要知道具体的构件。
装饰者模式是继承关系的一个替代方案。不管Component类被Decorator类装饰了多少层,返回的对象还是Component,实现的还是is - a的关系。
装饰者模式可以动态的扩展一个实现类的功能。
装饰者模式的缺点
会在设计中注入大量的小类,使得程序变得复杂,不易理解。
装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。
装饰者模式的要点
继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
在设计中,应该允许行为可以呗扩展,而无需修改现有的代码。
组合和委托可用于在运行时动态的加上新的行为。
装饰者模式模式意味着一群装饰者类,这些类用来包装具体的组件。
装饰者类反映出被装饰的组件类型(事实上,他们有相同的类型,都通过继承或者接口实现)
装饰者可以在被装饰着的前面或者后面加上自己的行为,甚至将被装饰者的行为完全取代掉,而达到特定的目的。
可以用无数个装饰者装饰一个组件。
装饰者一般对组件的客户是透明的,除非客户程序依赖组件的具体类型。
装饰者会导致设计中出现很多小对象,过度使用,会让程序变得复杂。
不贴代码了,但是说实话,可以看看java.io 里边类的设计。