前言
距离上次更完拖了2周,我也是记得说要2周更的。。。其实吧,本来应该是昨天晚上,今天上午就更完放出来的。谁知昨天接到新任务,要从头做一个蓝牙的demo,我怕会遇到坑所以就先去研究那个东西了,事实上也确实遇到坑了,还好昨晚通宵研究,现在解决了一部分了,剩下的东西不知道还有没有坑,但是还是想先把这个模式更完,毕竟说出去的话就得做到啊,说了这么多废话,开始吧!
装饰模式,就我个人的感觉看下来,有些微妙。真的有点微妙,因为这个模式确实在有些情况很好用很灵活,但是副作用也很大,让我这个强迫症无比纠结这个那个类到底放在哪。。。
装饰模式
一如既往的,先来说一下他的定义(headfirst中的定义)
装饰者模式(Decorator Pattern):动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
工厂模式帮助我们更好的去实例化pizza对象,单例模式帮我们解决了统一补给原料的问题。(记得之前我在工厂模式中说的,工厂适合和装饰者在一块使用么?终于填坑了。。)那装饰模式给我们带来了什么呢?
讲东西还是用例子形象,这里我继续用以前的那个Pizza来说吧(我总感觉在写代码的时候我特别钟爱Pizza。。。)
越来越多种类的Pizza
经营的pizza店经营的不错,也在推出越来越多种类的pizza,但渐渐的,你似乎发现了一些麻烦。。(以下代码都是在之前抽象工厂中写的)
1 | /** |
现在我们分析一下这段代码,有一个pizza超类,所有的pizza都继承自他,比如这里FishPizza继承了Pizza,这是一个很好的想法,可以获得父类Pizza的所有方法(忘记Pizza长什么样子的回抽象工厂那一节去看),不同的pizza也都只要重写父类的抽象方法prepare();就好了,似乎很perfect。
但是,每有一个种类的pizza,意味着你要新开一个xxPizza类,这样光说不明显,反面教材来了。FishPizza就像上面那样;那ChickenPizza显然就是将prepare中的fish变为chicken咯,没问题;继续,新引进的牛肉pizza呢?fish变成beef。可以,继续;哪天你脑门一拍FishChickenPizza!额。。。那就在prepare中即有fish,又要有chicken;好的,鱼牛、鸡牛、鱼鸡牛、鱼牛羊鸡xxxx呢?发现问题的的严重性了么?(其实也没有那么严重,不就是每次都要多写个几十行或者几百行嘛(╯‵□′)╯︵┻━┻),更重要的是:你每次都要重写prepare()方法!随着pizza的种类越来越多,会有一些迷之尴尬。
记得麦兜里面有一个桥段“鱼丸粗面”,电影里麦兜对没有的对象提出复用所以导致了麦兜要的东西都没有复用往往是在代码里很重要的一个部分,为什么要继承?因为可以复用父类的方法。而在上一段提出的方案中,prepare方法的复用率基本为零,这样显然是不好的。
装饰者登场
好了,看了上面这些,应该已经意识到了没法用继承来完全解决问题了:上面的问题有,子类数量爆炸;设计比较微妙;超类加入新功能的时候,那个功能未必适合所有类(一个方法有一半的子类用得到,另一半不应该有这个方法,微妙吧?一个个重写方法会死人的)
那回到做pizza上,该怎么做比较好呢?想想生活中的实际情况(代码永远是对生活的抽象,这句话是我自己说的)做个pizza会怎么做:先不管什么,揉个面团先做个饼!然后看具体情况要什么材料,然后一种一种的往饼上加,而不是套模板,哐当一下子就一整个做完了,遇到了新的组合还要去问别人怎么做(指要写一个新的完整的prepare方法)。
代码也是这样的,这里要以超类(最初的pizza,也就是一个面团)为主体,然后在运行的时候用不同的食材去“装饰”那个面团。好了那下面就用装饰模式来写一下吧!
1 | public class SimplePizza extends Pizza{ |
首先写一个基本的面团类,就是什么都没有的pizza,作为主体(最初的被装饰者),这个很简单,不用多说了。然后接下来就要写装饰者了:
1 | /** |
首先有个要注意的地方(划重点):装饰接口或者是抽象类,他必须和被装饰者有着相同的超类。既然装饰者和被装饰者对象都有着相同的超类,所以在任何需要原始对象(被修饰过的也行)的场合,就可以用装饰过的对象去替代他。
然后上面只写了2种具体的装饰类,其他的就类似了,xxxDecorator 这个类的构造方法需要接收一个Pizza实例,装饰者可以在所委托被装饰者的行为之前或者之后,加上自己的行为,来打到特地的目的,比如在上面的我写的例子中,我在构造方法中直接执行了重载的prepare,那么在xxxDecorator接收到上一个pizza对象后,就会调用,从而实现装饰的作用。好的下面来看看这些类是使用的:
1 | public class Test{ |
在上面的test类中,首先创造了什么都没有的pizza,然后加cheese,CheesePizza就好像他从别人那边拿到了一个面团,他的工作就是在面团放上cheese,然后面团就变成了一个带有cheese的面团;然后同样的 FishPizza就是从CheesePizza那拿到一个带cheese的面团,他的工作就是在上面放上fish于是面团就变成了一个 有 cheese、有fish的面团。通过这个例子可以看出,可以用一个或多个装饰者包装一个对象,对象也可以在任何时候被装饰。
I/O
这里不得不提一下I/O,java里面的io流,以前上学的时候听到这个东西就爆炸,看看类图,我曹这tm什么玩意!怎么这么多!其实,IO就是用装饰着模式写的。。。比如InputStream 是一个抽象的组件 (pizza),FilterInputStream是抽象装饰者(PizzaDecorator),BufferedInputStream就是具体的一个装饰者(xxxDecorator),具体的我就不细说了,就提一下,有兴趣的自己去翻jdk api。
一些小节
组合和继承
在上一小节开始的时候,我说了,不能用继承来解决问题了。这里也用到了继承,但是和一般的继承父类获得功能不一样。在装饰模式中,装饰者和被装饰者需要是一样的类型,所以用继承达到的目的并不是获得“行为”,而是为了“匹配类型”。所以说,装饰者和被装饰者实现同一个接口也是可以的。而获得的新的行为(比如加cheese)是装饰者与其他对象组合而获得的行为,与继承没有关系。如果行为依赖继承,那么只能在编译的时候静态的决定他的行为,换句话说,行为(也就是方法里的功能代码)不是来自超类,就是子类自己覆盖后的。反之,利用对象组合,可以把装饰着混着用,随心所欲的动态组合代码。
组件类型
如果把代码写成针对具体的组件类型,那么装饰者可能会出错,因为经过装饰者模式,类型就改变了,只有在针对抽象编程的时候,才不会因为装饰者而受到影响(可以看我上面的Test方法Pizza pizza = new SimplePizza();这里用的是Pizza的超类)
弊端
大家也可能发现了,刚刚在说io的时候就想吐槽了,每一个装饰者都有一个类,一个个琳琅满目,如果不熟悉可能会对这一对类感到一头雾水(其实不用装饰者可能会更多ㄟ( ▔, ▔ )ㄏ),然而装饰者模式的类都是一个个小类(仅有一部分功能并不能成为一个拥有完整“行为”的对象的类)
与工厂联动
这里就抛出一个概念,工厂是将对象创造的过程封装,而装饰者因为有很多小类 会有一些微妙,所以,可以把装饰者使用的的情况列举,在工厂方法中进行判断,然后根据不同的情况使用对象组合来实现对象的创建。说的直白点就是,只用装饰者是你自己根据判断往面团上丢cheese还是fish;而工厂+装饰者是 自动化流水线,你只要输入你要cheese 还是 其他什么的,工厂就直接给你,嗯就是这样
类图
本应该自己画的,可是真的好累,先用网上找到的挂着,有时间了再补一张上来
结语
我曹,我真想哭,写这个彻底超出我的预计时间,现在2017年07月08日01:55:57,,,我曹。。。我tmd,,我原计划12点之前写完,然后继续做蓝牙。谁知道我写着写着写偏了一次,重点放到组合上去了,,,tmd,啊啊啊啊啊!!!!!心累
估计到时候还要来一次修改,暂时先这样吧,也算没有鸽吧。。。。下次写什么模式还没有头绪,,,下次再说吧,,我去做蓝牙了。。