[b]一、引入[/b]
本篇可以称为“给爱用继承的人一个全新的设计眼界”。我们即将再度探讨典型的[b]继承滥用[/b]问题。你将在本章学到如何使用对象[b]组合[/b]的方式,做到在运行时装饰类。一旦你熟悉了装饰的技巧,你将能在不修改任何底层代码的情况下,给你的对象赋予新的职责。
以StarBuzz咖啡店为例,他们原先的类设计是这样的:
[img]http://dl2.iteye.com/upload/attachment/0099/8944/2042ee96-28e6-3361-828e-0cc4bf56a1fc.bmp[/img]
但需求扩展,购买咖啡时,可以要求在其中加入各种调料,如豆浆Soy,蒸奶Streamd Milk, 摩卡Mocha等,Starbuzz会根据所加入的调料收取不同的费用,所以订单系统必须考虑到这些调料部分。
这是他们的第一个尝试:
[img]http://dl2.iteye.com/upload/attachment/0099/8950/4ed51706-0bef-31a0-9ad0-91ef466ea171.bmp[/img]
这样是每个子类对应一种咖啡的搭配,一种价格的计算。
很明显,StarBuzz为自己制造了一个[color=red]维护[/color]恶梦。如果牛奶的价钱上扬怎么办?新增一种焦糖调料风味怎么办?
造成这种维护上的困难,因为违反了我们之前提过的设计原则:
(1)没有分离变化和不变化的部分。
(2)没有使用组合代替继承。
我们不只一次说过,软件工程生命周期里,[color=red]维护[/color]的代价是远高于[color=red]开发[/color]代价的。就算是开发过程中,回头维护之前的代码都是有很高代价的。所以软件设计一定要考虑维护的方便性。很明显,充分应用[b]设计原则[/b](设计模式都是设计原则的具体应用)可以减轻维护工作量。
[b]二、设计原则[/b]
此刻,我们再引入一个最重要的设计原则之一,[color=red][b]开闭原则[/b][/color]:
[b]类应该对扩展开放,对修改关闭。[/b]
[color=red]这是应对“改变”的一个重要原则。[/color]要改变的时候应该是可以轻松扩展,而不是修改已有代码。如果你的需求有所改变,欢迎用任何你想要的行为来扩展我们的类。但不允许随意修改代码,我们花了许多时间得到了正确的代码,还解决了所有的bug,所以不让你修改现有的代码。
这乍听起来很矛盾,可是想想前面介绍的观察者模式,需要新增观察者时,不需要修改主题(Subject)就可以任何时候扩展观察者。我们本次要介绍的装饰者模式也是完全符合开闭原则的。
但是如何让设计的每个部分都遵循开放-关闭原则?通常这是办不到的。要让面向对象的设计同时具备开放性和关闭性,又不能修改现有的代码需要花费许多时间和努力。遵循开闭原则通常会引入新的抽象层次,增加代码的复杂度。我们需要把注意力集中在设计中最有可能改变的地方。然后应用开闭原则。
[b]三、装饰者模式[/b]
结合上Starbuzz的咖啡问题,我们要采用不一样的做法:我们以饮料(Beverage类)为主体,然后在运行时以调料来装饰(decorate)饮料。比如,顾客想要摩卡和奶泡深焙咖啡,那么我们要做的是:
(1) 拿一个深焙咖啡(DarkRoast)对象
(2) 以摩卡(Mocha)对象装饰它
(3) 以奶泡对象(Whip)装饰它
(4) 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去
下面看下设计:
[img]http://dl2.iteye.com/upload/attachment/0099/8952/50da3204-cfb6-3344-bd23-147012abbf99.bmp[/img]
[img]http://dl2.iteye.com/upload/attachment/0099/8954/8dcebb70-a70c-34de-a44e-2f74a8f53bb8.bmp[/img]
好了,我们通过上面的例子已经知道:
(1) 装饰者和被装饰者对象有[b]相同的超类型[/b]
(2) 可以用一个或多个装饰者包装一个对象
(3) 既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
(4) 装饰者可以在所委托被装饰者的行为之前或之后加上自己的行为,以达到特定的目的。
(5) 对象可以在任何时候被装饰,所以可以再运行时动态地不限量地用自己喜欢的装饰者来装饰对象。
现在,我们可以试着定义下[color=red][b]装饰者模式:[/b][/color]
动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
下面我们看下装饰者典型的类图:
[img]http://dl2.iteye.com/upload/attachment/0099/8956/0e35812b-4673-3eb3-b5a9-18a41ee89298.bmp[/img]
我们发现,这里面也用到继承了!!!!
[color=red]仔细分析我们会发现我们用到继承只是达到装饰者和被装饰者是同一个超类型的目的,而不是想通过继承来获取行为。具体的行为是通过关联(组合)获取的。[/color]
[b]真实世界里的装饰者[/b]
Java IO里装饰真模式应用很多,比如BufferedInputStream和LineNumberInputStream都是具体的装饰者类,继承自抽象的装饰类FilterInputStream。
[b]四、总结[/b]
1.现在来看我们已经见过的设计原则:
(1)封装变化
(2)多用组合少用继承
(3)针对接口编程,不针对实现编程
(4)为交互对象的松耦合设计而努力
(5)对扩展开放,对修改关闭。
2.设计模式
(1)策略模式
(2)观察者模式
(3)装饰者模式
参考资料:
《HeadFirst》
本篇可以称为“给爱用继承的人一个全新的设计眼界”。我们即将再度探讨典型的[b]继承滥用[/b]问题。你将在本章学到如何使用对象[b]组合[/b]的方式,做到在运行时装饰类。一旦你熟悉了装饰的技巧,你将能在不修改任何底层代码的情况下,给你的对象赋予新的职责。
以StarBuzz咖啡店为例,他们原先的类设计是这样的:
[img]http://dl2.iteye.com/upload/attachment/0099/8944/2042ee96-28e6-3361-828e-0cc4bf56a1fc.bmp[/img]
但需求扩展,购买咖啡时,可以要求在其中加入各种调料,如豆浆Soy,蒸奶Streamd Milk, 摩卡Mocha等,Starbuzz会根据所加入的调料收取不同的费用,所以订单系统必须考虑到这些调料部分。
这是他们的第一个尝试:
[img]http://dl2.iteye.com/upload/attachment/0099/8950/4ed51706-0bef-31a0-9ad0-91ef466ea171.bmp[/img]
这样是每个子类对应一种咖啡的搭配,一种价格的计算。
很明显,StarBuzz为自己制造了一个[color=red]维护[/color]恶梦。如果牛奶的价钱上扬怎么办?新增一种焦糖调料风味怎么办?
造成这种维护上的困难,因为违反了我们之前提过的设计原则:
(1)没有分离变化和不变化的部分。
(2)没有使用组合代替继承。
我们不只一次说过,软件工程生命周期里,[color=red]维护[/color]的代价是远高于[color=red]开发[/color]代价的。就算是开发过程中,回头维护之前的代码都是有很高代价的。所以软件设计一定要考虑维护的方便性。很明显,充分应用[b]设计原则[/b](设计模式都是设计原则的具体应用)可以减轻维护工作量。
[b]二、设计原则[/b]
此刻,我们再引入一个最重要的设计原则之一,[color=red][b]开闭原则[/b][/color]:
[b]类应该对扩展开放,对修改关闭。[/b]
[color=red]这是应对“改变”的一个重要原则。[/color]要改变的时候应该是可以轻松扩展,而不是修改已有代码。如果你的需求有所改变,欢迎用任何你想要的行为来扩展我们的类。但不允许随意修改代码,我们花了许多时间得到了正确的代码,还解决了所有的bug,所以不让你修改现有的代码。
这乍听起来很矛盾,可是想想前面介绍的观察者模式,需要新增观察者时,不需要修改主题(Subject)就可以任何时候扩展观察者。我们本次要介绍的装饰者模式也是完全符合开闭原则的。
但是如何让设计的每个部分都遵循开放-关闭原则?通常这是办不到的。要让面向对象的设计同时具备开放性和关闭性,又不能修改现有的代码需要花费许多时间和努力。遵循开闭原则通常会引入新的抽象层次,增加代码的复杂度。我们需要把注意力集中在设计中最有可能改变的地方。然后应用开闭原则。
[b]三、装饰者模式[/b]
结合上Starbuzz的咖啡问题,我们要采用不一样的做法:我们以饮料(Beverage类)为主体,然后在运行时以调料来装饰(decorate)饮料。比如,顾客想要摩卡和奶泡深焙咖啡,那么我们要做的是:
(1) 拿一个深焙咖啡(DarkRoast)对象
(2) 以摩卡(Mocha)对象装饰它
(3) 以奶泡对象(Whip)装饰它
(4) 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去
下面看下设计:
[img]http://dl2.iteye.com/upload/attachment/0099/8952/50da3204-cfb6-3344-bd23-147012abbf99.bmp[/img]
[img]http://dl2.iteye.com/upload/attachment/0099/8954/8dcebb70-a70c-34de-a44e-2f74a8f53bb8.bmp[/img]
好了,我们通过上面的例子已经知道:
(1) 装饰者和被装饰者对象有[b]相同的超类型[/b]
(2) 可以用一个或多个装饰者包装一个对象
(3) 既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
(4) 装饰者可以在所委托被装饰者的行为之前或之后加上自己的行为,以达到特定的目的。
(5) 对象可以在任何时候被装饰,所以可以再运行时动态地不限量地用自己喜欢的装饰者来装饰对象。
现在,我们可以试着定义下[color=red][b]装饰者模式:[/b][/color]
动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
下面我们看下装饰者典型的类图:
[img]http://dl2.iteye.com/upload/attachment/0099/8956/0e35812b-4673-3eb3-b5a9-18a41ee89298.bmp[/img]
我们发现,这里面也用到继承了!!!!
[color=red]仔细分析我们会发现我们用到继承只是达到装饰者和被装饰者是同一个超类型的目的,而不是想通过继承来获取行为。具体的行为是通过关联(组合)获取的。[/color]
[b]真实世界里的装饰者[/b]
Java IO里装饰真模式应用很多,比如BufferedInputStream和LineNumberInputStream都是具体的装饰者类,继承自抽象的装饰类FilterInputStream。
[b]四、总结[/b]
1.现在来看我们已经见过的设计原则:
(1)封装变化
(2)多用组合少用继承
(3)针对接口编程,不针对实现编程
(4)为交互对象的松耦合设计而努力
(5)对扩展开放,对修改关闭。
2.设计模式
(1)策略模式
(2)观察者模式
(3)装饰者模式
参考资料:
《HeadFirst》