装饰者模式编程
1、我曾经觉得男子汉应该用继承处理一切,后来我领教到运行时扩展,远比编译时期的继承威力大,看看我如今光彩的样子设计模式
2、“给爱用继承的人一个全新的设计眼界”,咱们即将再度讨论典型的继承滥用问题,如何使用对象组合的方式,作到在运行时装饰类。为何呢?一旦你熟悉了装饰的技巧,你将可以在不修改任何底层代码的状况下,给你的(或别人的)对象赋予新的职责学习
星巴兹咖啡ui
1、这是一个以扩张速度最快而闻名的咖啡连锁店spa
2、由于扩张速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求设计
原先的类设计是这样的component
1、Beverage(饮料)是一个抽象类,店内所提供的饮料都必须继承自此类对象
2、这个名为description(叙述)的实例变量,由每一个子类设置,用来描述饮料,例如“超优深焙(Dark Roast)咖啡豆”继承
3、cost()方法是抽象的子类必须定义本身的实现接口
4、每一个子类实现cost()来返回饮料的价钱
需求来了
购买咖啡时,也能够要求在其中加入各类调料,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不一样的费用。因此订单系统必须考虑到这些调料部分
这是第一次尝试
1、每一个cost()方法将计算出咖啡加上订单上各类调料的价钱
2、哇塞!这简直是“类爆炸”
3、很明显,星巴兹为本身制造了一个维护噩梦。若是牛奶的价钱上扬,怎么办?新增一种焦糖调料风味时,怎么办?
是呀,干吗这几这么多类?利用实例变量和继承,就能够追踪这些调料呀!
好吧!就来试试看,先从Beverage基类下手,加上实例变量表明是否加上调料(牛奶、豆浆、摩卡、奶泡... ...)
1、各类调料的新的布尔值
2、如今,Beverage类中的cost()再也不是一个抽象方法,咱们提供了cost()的实现,让它计算要加入各类饮料的调料价钱。子类仍将覆盖cost(),可是会调用超类的cost(),计算出基本饮料加上调料的价钱
3、这些方法取得和设置调料的布尔值
1、如今加入子类,每一个类表明菜单上的一种饮料
2、超类cost()将计算全部调料的价钱,而子类覆盖过的cost()会扩展超类的功能,把指定的饮料类型的价钱也加进来
3、每一个cost()方法须要计算该饮料的价钱,而后经过调用超类的cost()实现,加入调料的价钱
经过思考设计未来可能须要的变化,能够看出来这种方法有一些潜在的问题
1、调料价钱的改变会使咱们更改现有代码
2、一旦出现新的调料,咱们就须要加上新的代码,并改变超类中的cost()方法
3、之后可能开发出新的饮料,对这些饮料而言(例如:冰茶),某些调料可能并不适合,可是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)
4、万一顾客想要双倍摩卡咖啡,怎么办?
大师和门徒
大师:我说蚱蜢呀!举例咱们上次见面已经有些时日,你对于继承的冥想,可有精进?
门徒:是的,大师。尽管继承威力强大,可是我体会到它并不老是可以实现最优弹性和最好维护的设计
大师:啊!是的,看来你已经有所长进。那么,告诉我,个人门徒,不经过继承又能如何达到复用呢?
门徒:大师,咱们已经了解到利用组合(composition)和委托(delegation)能够在运行时具备继承行为的效果
大师:好,好,继续... ...
门徒:利用继承设计子类的行为,是在编译时静态决定的,并且全部的子类都会继承到相同的行为。然而,若是可以利用组合的作法扩展对象的行为,就能够在运行时动态地进行扩展
大师:很好,蚱蜢,你已经开始看到组合得为例了
门徒:是的,我能够利用此技巧把多个新职责,甚至是设计超类时尚未想到的职责加在对象上。并且,能够不用修改原来的代码
大师:利用组合维护代码,你认为效果如何?
门徒:这正是我要说的。经过动态地组合对象,能够写新的代码添加新功能,而无需修改现有代码。既然没有改变现有代码,那么引进bug或产生之外反作用的机会将大幅度减小
大师:很是好。蚱蜢今天的谈话就到这里。但愿你能在这个主题上更深刻... ... 牢记,代码应该如同晚霞中的睡莲同样地关闭(免于改变),如同晨曦中的莲花同样地开放(可以扩展)
开放——关闭原则
1、设计原则——类应该对扩展开放,对修改关闭
2、请进,如今"开放"中,欢迎用任何你想要的行为来扩展咱们的类。若是你的须要或需求有所改变(咱们知道这必定会发生的),那就来吧!动手扩展吧!
3、抱歉,如今是"关闭"状态。没错。咱们花了许多时间获得了正确的代码,还解决了全部的bug,因此不能让你修改现有的代码。咱们必须关闭代码以防止被修改。若是你不喜欢,能够找经理谈
4、咱们的目标是容许类容易扩展,在不修改现有代码的状况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具备弹性能够应对改变,能够接受新的功能来应对改变的需求
对扩展开放,对修改关闭?听起来很矛盾。设计如何兼顾二者?
1、这是一个很好的问题,咋听之下,的确感到矛盾,毕竟,越难修改的事物,就越难以扩展,不是吗?
2、可是,有些聪明的OO技巧,容许系统在不修改代码的状况下,进行功能扩展。想一想观察者模式,经过加入新的观察者,咱们能够在任什么时候候扩展Subject(主题),并且不需向主题中添加代码。之后,你还会陆续看到更多的扩展行为的其余OO设计技巧
好吧!我了解观察者模式,可是该如何将某件东西设计成能够扩展,又禁止修改?
许多模式是长期经验的实证,可经过提供扩展的方法来保护代码免于被修改。
装饰者模式就是遵循开放——关闭原则的一个很好的例子
我如何让设计的每一个部分都遵循开放——关闭原则?
一般,你办不到。要让OO设计同时具有开放性和关闭性,又不修改现有的代码,须要花费许多时间和努力。通常来讲,咱们实在没有闲工夫把设计的每一个部分都这么设计(并且,就算作获得,也可能只是一种浪费)。遵循开放——关闭原则,一般会引入新的抽象层次,增长代码的复杂度。你须要把注意力集中在设计中最有可能改变的地方,而后应用开放——关闭原则
我怎么知道,哪些地方的改变是更重要呢?
这牵涉到设计OO系统的经验,和对你工做领域的了解。多看一些其余的例子能够帮你学习如何辨别设计中的变化区
总结
虽然彷佛有点矛盾,可是的确有些技术能够容许在不直接修改代码的状况下对其进行扩展
在选择须要被扩展的代码部分时要当心。每一个地方都采用开放——关闭原则时一种浪费,也不必,还会致使代码变得复杂且难以理解
认识装饰者模式
1、装饰者和被装饰对象有相同的超类型
2、你能够用一个或多个装饰者包装一个对象
3、既然装饰者和被装饰对象有相同的超类型,因此在任何须要原始对象(被包装的)的场合,能够用装饰过的对象代替它
4、装饰者能够在所委托被装饰者的行为以前与/或以后,加上本身的行为,以达到特定的目的
5、对象能够在任什么时候候被装饰,因此能够在运行时动态地、不限量地用你喜欢的装饰者来装饰对象
定义装饰者模式
装饰者模式——动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案
1、每一个组件均可以单独使用,或者被装饰器包起来使用
2、ConcreteComponent是咱们将要动态地加上新行为的对象,它扩展自Component
3、每一个装饰者都"有一个"(包装一个)组件,也就是说,装饰者有一个实例变量以保存某个Component的引用
4、装饰者功能共同实现的接口(也能够是抽象类)
5、ConcreteDecorator有一个实例变量,能够记录所装饰的事物(装饰者包着的Component)
6、装饰者能够扩展Component的状态
7、装饰者能够加上新的方法。就行为是经过在旧行为前面或后面作一些计算来添加的
装饰咱们的饮料
哎呀!我有点混淆... ..我原觉得在这个模式中不会使用继承,而是要利用组合取代继承
Sue:这话怎么说?
Mary:看看类图。CondimentDecorator扩展自Beverage类,这用到了继承,不是吗?
Sue:的确是如此,但我认为,这么作的重点在于,装饰者和被装饰者必须是同样的类型,也就是有共同的超类,这是至关关键的地方。在这里,咱们利用继承达到“类型匹配”,而不是利用继承得到“行为”
Mary:我知道为什么装饰者须要和被装饰者(即被包装的组件)有相同的“接口”,由于装饰者必须能取代被装饰者,可是行为又是从哪里来的?
Sue:当咱们将装饰者与组件组合时,就是在加入新的行为。所获得的新行为,并非继承自超类,而是由组合对象的来的
Mary:好的。继承Beverage抽象类,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其余装饰者之间的组合关系
Sue:正是如此
Mary:哦!我明白了。并且由于使用对象组合,能够把全部饮料和调料更有弹性地加以混合与匹配,很是方便
Sue:是的。若是依赖继承,那么类得行为只能在编译时静态决定。换句话说,行若是不是来自超类,就是子类覆盖后的版本。反之,利用组合,能够把装饰者混合着用... ...并且是在"运行时"
Mary:并且,如我所理解的,咱们能够在任什么时候候,实现新的装饰者增长新的行为。若是依赖继承,每当须要新行为时,还的修改现有的代码
Sue:的确如此
Mary:我还剩下一个问题,若是咱们须要继承的是component类型,为何不Beverage类设计成一个接口,而是设计成一个抽象类呢?
Sue:关于这个嘛,还记得吗?当初咱们从星巴兹拿到这个程序时,Beverage已是一个抽象类了。一般装饰者模式是采用抽象类,可是在Java中可使用接口。尽管如此,一般咱们都努力避免修改现有的代码,因此,若是抽象类运做的很好,仍是别去修改它
装饰者的告白
HeadFirst:欢迎装饰者模式,据说你最近情绪有点差?
装饰者:是的,我知道你们都认为我是一个有魅力的设计模式,可是,你知道吗?我也有本身的困扰,就和你们同样
HeadFirst:愿意让咱们分担一些你的困扰吗?
装饰者:固然能够。你知道我有能力为设计注入弹性,这是毋庸置疑的,可是我也有“黑暗面”。有时候我会在设计中加入大量的小类,这偶尔会致使别人不一样意了解个人设计方式
HeadFirst:你可以举个例子吗?
装饰者:以Java I/O库来讲,人们第一次接触到这个库时,每每没法轻易地理解它。可是若是他们能认识到这些类都是用来包装InputStream的,一切都会变得简单多了
HeadFirst:听起来并不严重。你仍是一个很好的模式,只须要一点点的教育,让你们知道怎么用,问题就解决了
装饰者:恐怕不止这些,我还有类型问题。有些时候,人们在客户代码中依赖某种特殊类型,而后突然导入装饰者,却又没有周详地考虑一切。如今,个人一个优势是,你一般能够透明地插入装饰者,客户程序甚至不须要知道它是在和装饰者打交道。可是,如我刚刚所说的,有些代码会依赖特定的类型,而这样的代码一导入装饰者,嘭!出情况了!
HeadFirst:这个嘛,我相信每一个人都必须了解到,在插入装饰者时,必需要当心谨慎。我不认为这是你的错!
装饰者:我知道,我也试着不这么想。我还有一个问题,就是采用装饰者在实例化组件时,将增长代码的复杂度。一旦使用装饰者模式,不仅须要实例化组件,还要把此组件包装进装饰者中,天晓得有几个
HeadFirst:我下周会访谈工厂(Factory)模式和生成器(Builder)模式,我据说他们对这个问题有很大的帮助
装饰者:那却是真的。我应该常和这些家伙聊聊
HeadFirst:咱们都认为你是一个好的模式,适合用来创建有弹性的设计,维持开放——关闭原则,你要开心一点,别负面思考
装饰者:我尽可能把。谢谢你
再看OO原则
1、封装变化
2、多用组合,少用继承
3、针对接口编程,不针对实现编程
4、为交互对象之间的松耦合设计而努力
5、对扩展开放,对修改关闭
装饰者模式定义
动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另外一种选择
要点
1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式
2、在咱们的设计中,应该容许行为能够被扩展,而无须修改现有的代码
3、组合和委托可用于在运行时动态地加上新的行为
4、除了继承,装饰者模式也可让咱们扩展行为
5、装饰者模式意味着一群装饰者类,这些类用来包装具体组件
6、装饰者类反应出被装饰的组件类型(事实上,他们具备相同的类型,都通过接口或继承实现)
7、装饰者能够在被装饰者的行为前面与/后后面加上本身的行为,甚至将被装饰者的额行为整个取代掉,而达到特定的目的
8、你能够用无数个装饰者包装一个组件
9、装饰者通常对组件的客户是透明的,除非客户程序依赖于组件的具体类型
10、装饰者会致使设计中出现许多小对象,若是过分使用,会让你的程序变得很复杂