学习Head First Design Pattern——翻译Chapter 3:The Decorator Pattern

学习翻译Head First Design Pattern——Chapter 3The Decorator Pattern

 

Page 79

装饰对象

图〉〉略

    男:我过去认为真正的程序员就是总是使用继承,直到我认识到在运行时而不是在编译时扩展的魔力。现在跟我来……

这章我们不妨叫做“给迷恋继承的伙计们的设计教程”

我们将重新提及一下关于继承被过度使用的典型设计问题,然后给你们讲讲如何使用对象组合的方式来在运行时装饰你们的对象。为什么呢?一旦你学会了装饰技术,你将能够已经存在的对象(不论是你的或是别的什么人的)添加新的功能,关键在于你还不需要改变基类的任何代码。

 

Page 80

       欢迎来到Starbuzz(星巴克)咖啡吧

       Starbuzz咖啡吧是在全球范围内迅速发展的一个著名的咖啡吧。如果你在附近的街角看见一间店,那么朝对面看看,还会有另外一间。

       因为它发展的很快,为了适应饮料消费量,它急需更新结账系统。

       当它刚开始营业时候,他的结账系统这样设计类如下:

       Beverage是个抽象类,咖啡吧提供的所有的饮料都从它派生。

       Description实例变量在每个子类中设置它的值,它保存这该饮料的具体说明信息,比如:最出色的Dark Roast。方法GetDescription用于获取这个描述。

       方法Cost也是抽象的,子类需要定义他们自己的实现。

       每个子类都实现Cost方法来返回自身的饮料价钱。

 

Page 81

       此外,你还可以给你的咖啡加一些调料,像:Steamed Milk(蒸过的牛奶)、Soy(豆酱)和巧克力,然后再在最上层加上生牛奶。在Starbuzz店里,所有这些额外调料品都是收费的,因此他们在构建结账系统时候必须考虑到这些调料。

       图〉〉类爆炸啦:略

 

Page 82

想一想〉〉

很显然,Starbuzz的维护工作犹如噩梦一般。当牛奶价格上涨时会发生什么呢?当他们添加一个新的调料——焦糖时他们要作些什么呢?

仔细考虑一下维护问题,它都破坏了那些我们已经讲解过的设计原则呢?

图〉〉略

    女:真笨,我们根本就不需要这么多的类!我们只需要把这些调料作为基类的实例变量,然后再由子类继承进行管理。

那么,好吧,让我们试试这个想法。我们给基类Beverage中添加一些布尔变量来标示该饮料是否外加牛奶、豆酱、巧克力和焦糖等调料。

现在每个调料都有了对应的布尔实例变量。

现在cost不再是抽象的了,在Beverage基类中该方法有自己的具体实现。子类重载该方法,它们通过调用基类的Cost方法来计算整个饮料包括调料在内的总价格。

 

Page 83

基类的Cost方法计算所有的调料价格,然后在子类的重载版本中再加上具体饮料的基本价格。

练习:磨磨你的铅笔尖〉〉

为下列类写出Cost方法的伪代码:

Public class Beverage

{

   Public double cost()

   {

      Double condimentsPrice=0;

      If(true==hasMilk())   condimentsPrice+=milkPrice;

      If(true==hasSoy())   condimentsPrice+=soyPrice;

      If(true==hasMocha())   condimentsPrice+=mochaPrice;

      If(true==hasWhip())   condimentsPrice+=whipPrice;

      Return condimentsPrice;

   }

}

Public class DarkRoast extends Beverage

{

   Public DarkRoast()

   {

     Description = “Most Excellent Dark Roast”;

   }

   Public double cost()

   {

     Return Base::cost()+darkRoastPrice;

   }

}

 

Page 84

图〉〉略

女:看看,只剩5个类了,显然我们应该这样做!

男:我并不太肯定这种做法,因为如果在将来还有改变的话,使用该方法就有些潜在的问题了。

 

练习:磨磨你的铅笔尖〉〉

会有什么需求或者其他因素会发生改变进而影响上面的这个设计方案呢?

1、  调料的价格变化将会迫使我们改变现有代码

2、  加入新的调料时,我们必须给基类添加新的方法并且改变cost方法的实现

3、  当有新的饮料种类并不需要任何现有的调料,比如像冰茶,然而Tea子类还必须继承这些调料方法像hasWhip

4、  如果用户想要双份巧克力呢?

//现在轮到你写了……

    哈哈,我想不出来了……抱歉

Page 85

       大师和学徒的交流

       大师:小朋友,我们有段日子没见了。你仔细考虑过继承吗?

学徒:是的,师傅。虽然继承很强大,但是我已经知道了它并不总是能够带来高柔性和易维护的设计方案了。

大师:是吗?看来你进步了。现在,你给我说说,如果不使用继承,你将如何实现重用。

学徒:师傅,我现在知道些在运行时继承行为的方法,比如组合和代理。

大师:继续讲……

学徒:当我通过派生继承行为时,那样行为只是在编译时的静态继承。此外,所有的子类必须继承相同的行为。但是,我可以通过组合扩展对象的行为,甚至在运行时动态扩展。

大师:非常好,小朋友,你已经开始了解组合的威力了。

学徒:是的,使用这种技术,我可以对象添加多个新的责任,包括一些我们在使用派生设计时无法考虑实现的责任。并且,我不需要更改原有代码。

大师:你对使用组合技术帮助你维护代码有何感受呢?

学徒:那是我正在钻研的。通过动态地组合对象,我可以通过写新的代码而不是更改已有代码来添加新的功能。由于不改动已有代码,这样引入bug或者无意负面作用的几率就大大下降了。

大师:太好了。今天就到这儿吧。我希望你能够进一步钻研这个主题……记住:已有代码在面对变化时候应当保持不变,但当需要扩展时却应当敞开大门,就像莲花,晚包早开一样。

 

Page 86

       OPENCLOSED原则

       学徒们注意了,这是个最重要的设计原则:

              类设计应当支持扩展且杜绝修改!

       进来吧!门开着。如果你想扩展我们的功能就请自便吧。如果需求改变或者你需要改变,只要你通过扩展,这些目的都可以达到的。

       对不起,我们不欢迎你。我们费了九牛二虎之力才保证这些代码正常运行并且没有bug,因此,我们决不允许你更改已有的代码。我必须保证代码不被修改。如果你不喜欢这样,你可以和经理交涉。

       我们的目标是:让类能够轻松扩展以添加新的行为,但前提是不改动已有的代码。如果我们做到这一点,那么我们会获得什么好处呢?答案是我们可以让设计满足不断变化的需求,因为它有足够弹性应对变化并且有足够的柔性来添加新的功能。

 

Page 87

       必须提及的问题:

       Q1:支持扩展却杜绝修改,这听起来有些自相矛盾。一个设计如何同时做到这两点呢?

A1:问得好!乍听起来的确有些自相矛盾,毕竟,如果一个东西不易修改,它显然也很难扩展,对吗?

       但是,有些已经得到验证的巧妙的OO设计技术允许我们在不改变基础代码的基础上进行系统的扩展。想想第二章的OBSERVER模式,通过增加新的观测者,我们可以在任何时刻扩展Subject,而且没有给Subject添加任何代码。你还会看到相当多的使用其它设计原则进行扩展行为的方法。

Q2:我理解那个模式,但是我如何得到一个通用设计,它既支持扩展且又杜绝修改呢?

A2:很多模式让我们能够有时间测试我们的设计在支持扩展的同时又保护原有代码不被修改。本章的DECORATOR模式就是一个遵循该OPEN-ClOSED原则的很好的例子。

Q3:我如何让我的设计的每个细节都遵循这个OPEN-CLOSED原则呢?

A3:一般来讲,这是不可能的。作出一个易扩展且又不改动原有代码的OO设计需要付出很大的努力和时间代价。我们并不奢望我们设计的每个细节都遵循这个原则,而且一定程度上将,这样做会比较浪费资源。遵循这个OPEN-CLOSED原则就会给引入更高层次的新的抽象概念,正是这些给我们的代码实现带来了复杂性。你应当弄清楚在你的整个设计中,那些地方最容易发生变化,然后再集中精力在这里运用这个原则。

Q4:那么,我又如何知道我的设计中那些地方的变化比较重要呢?

A4:这就必须依靠你设计OO系统的经验和对你设计的问题领域的理解程度了。学习已有的相似例子可以帮助你识别出你自己设计中比较重要的地方。

 

尽管这个原则听起来有些自相矛盾,但是有很多技术可以让我们的代码易于扩展而又不直接修改。

       仔细决定你的设计中需要扩展的地方;因为在每个细节都运用这个OPEN-CLOSED原则是浪费的,不必要的,并且会引入复杂因素,从而使我们的代码难以理解。

 

Page 88

       来看看DECORATOR模式吧

图〉〉略

   男:好了,OO设计俱乐部的人已经够多了。我们有现成的问题摆在这里,还记得我们吗?Starbuzz咖啡吧的?你认为你可以使用你所讲解的设计原则来帮助我们解决实际问题吗?

       好的,我们也经看到,使用继承表达饮料和调料的价格是不合适的,那样会导致类爆炸。或许把调料价格计算作为基类的功能可以,但是遗憾的是对于一些子类它不太适合。

       因此,我们在这里给出一个新的解决办法:我们开始时只有Beverage,然后在运行时使用调料来装饰Decorate它。例如:有顾客想要个带有巧克力和焦糖的Dark Roast,那么,我们按照下面的步骤作:

1、  获取一个Dark Roast对象;

2、  使用一个Mocha对象装饰它;

3、  使用一个Whip对象装饰它;

4、  调用Cost方法然后依靠代理Delegation来把调料的价格加进来。

很好!但是,你如何“装饰”一个对象呢?而且代理如何应用呢?给个提示:把装饰对象的动作想象为“包裹或者包装”。让我们看看“装饰”是如何工作的……

 

Page 89~90

       使用装饰技术调配一种饮料

1、  刚开始我们只有个DarkRoast对象;

还记得吗?DarkRoastBeverage派生,因此它有计算饮料价格的方法Cost

2、  顾客想要加点巧克力,我们创建一个Mocha对象然后用它把DarkRoast对象包起来。

Mocha对象是个装饰品。它和它所装饰的对象的类型相同,即Beverage,因此Mocha也有Cost方法,并且通过多态,我们可以把任何添加了MochaBeverage仍然看作Beverage,因为Mocha本身也是Beverage

3、  顾客又想要加些焦糖,我们创建一个Whip对象,然后用它把Mocha对象包起来。

Whip也是个装饰品,所以它和DarkRoast的基类型一样,也有Cost方法。因此,当添加了MochaWhipDarkRoast仍然是Beverage并且我们可以像计算DrakRoast的价格一样调用加入了调料的DarkRoast的价格计算方法Cost

4、  现在顾客要结账了。我们通过调用最外层Decorator——Whip的方法Cost,此时由Whip代理计算它本身装饰的对象的价格。一旦它得到一个计算结果,它会把自身的价格再加进去,这就是整个饮料的价格。

a)         首先调用WhipCost

b)        Whip调用MochaCost

c)        Mocha调用DarkRoastCost

d)        DarkRoast返回它的价格¥99

e)         Mocha把自身价格¥20加到DarkRoast返回的结果里,然后返回¥119

f)         Whip把自身价格¥10加到Mocha返回的结果,然后返回¥129

好的,现在来看看到现在为止我们都了解了些什么?

l       “装饰品”和它所装饰的对象具有相同的基类型;

l       可以在一个对象外部添加多个装饰品;

l       既然装饰品和它装饰的对象具有相同的基类型,那么我们就可以使用装饰品对象来替代原有对象——即被装饰的对象;

l       [关键点]无论在代理它所装饰对象作剩余工作之前和/或者之后,装饰品都可以添加它们自己的行为。(The Decorator adds its own behavior either before and/or after delegating to the object it decorates to do the rest of the job.)

l       对象可以在任何时刻被装饰。所以我们可以在运行时使用任意多的装饰品来动态的装饰对象。

现在,我们来看看这些都如何在实际工作中应用:首先给出DECORATOR模式的定义,然后试着写些代码。

 

Page 91

       DECORATOR模式的定义

       我们先看看模式DECORATOR的描述:

DECORATOR模式可以动态地为对象附加功能。它为功能扩展提供了一种替代继承的灵活方式。(Decorators provide a flexible alternative to subclassing for extending functionally.

       但是对模式DECORATOR的描述并没有给我们如何在我们的自己的实现中应该它的提示。下面让我们来看看该模式所对应的类图:它会为我们揭示一些如何应用的信息(下一页,我们会看到如何在Beverage中应用该模式的类图设计)。

       每个Component可以单独使用,或者使用装饰品包装。

       每个Decorator都有HAS-A即包装了一个Component,这就意味着Decorator有个实例变量来引用它所包装的Component

       具体的Component就是我们打算动态添加新行为的对象,它扩展了Component

       所有的Decorator都必须实现它们要包装的Component的接口或者抽象类。

       具体的Decorator都有个实例变量来引用它们装饰的对象。

       Decorator中可以扩展Component的状态,此外还可以添加全新的方法;尽管如此,新的行为一般情况下都是在调用Component已有方法之前或者之后添加。(new behavior is typically added by doing computation before or after an existing method in the compoent

 

Page 92

       应用DECORATOR模式解决Beverage问题

       现在,让我们应用上一页的模式框架图来解决StarbuzzBeverage问题

想一想〉〉

在继续之前,好好想想你如何实现咖啡和调料的Cost方法。还有如何实现调料的GetDescription方法。

 

      

 

Page 93

       小房间的探讨

       一些继承和组合之间的迷惑

图〉〉略

Mary:我有些迷糊了我原以为在这个模式中不会使用继承,而是应该只靠组合

       Sue:你想说些什么呢?

       Mary:看看这个类图。调料装饰品是从Beverage派生的,这不是继承吗?

Sue:是的,我认为在这里使用继承非常必要,因为Decorator和它所装饰的对象必须有相同的基类型。而使用继承可以做到类型匹配,但是注意我们并没有使继承来获取行为。

Mary:我理解Decorator和它所装饰的Component需要有相同的接口,因为它们需要代理或者说代替Component。但是新的行为如何引入呢?

Sue:我们在组合ComponentDecorator时候引入新的行为。我们并不是通过派生基类获取新的行为而是通过组合对象。

Mary:哦!所以我们通过派生抽象类Beverage获取正确类型但不是为了继承它的行为。在组合DecoratorComponent时候引入行为。

Sue:是的。

Mary:噢噢,我理解了。因为我们使用对象组合技术,我们就可以更加灵活去混合和匹配调料和饮料。真是巧妙。

Sue:是的,如果我们采用继承,我们的行为在编译时刻就已经决定了。也就是说:我们只能获取基类派生给我们或者我们重载的行为。而使用组合,我们可以在任何时刻通过任何方式附加新的行为。

Mary:按照我的理解,我们就可以在任意时刻实现新的Decorator来添加新的行为。如果我们使用继承,我们要想添加新的行为,就必须修改已有的代码。

Sue:非常贴切。

Mary:我还有个问题:既然所有的都必须继承Component,为什么不使用接口来代替抽象类Beverage呢?

Sue:你还记得吗:Starbuzz提供给我们的代码中就已经有了抽象类Beverage。在DECORATOR模式中我们需要描述抽象的Component,在Java中可以使用接口来实现。但是我们经常要避免改变已有的代码,如果它们运行的很好,那么就不要改动它。

 

Page 94

       练习使用DECORATOR模式构建一种新的饮料

       配方:HouseBlend咖啡,外加一份豆酱、一份焦糖和两份巧克力。

       图〉〉略

 

Page 95

       Starbuzz写系统代码

       是时候把这个设计付诸于具体代码了!

       让我们从抽象类Baverage开始吧,

                   //抽象类,有两个方法和一个实例变量

              Public abstract class Beverage

              {

                     String description = “Unknown Beverage!”;

                     Public string GetDescription()

                     {

                                     //GetDescription已经有了实现,但是我们必须在子类中有自己的实现

                            Return description;

                     }

                     Public abstract double Cost();

              }

       Beverage 很简单,下面让我们实现Decorator即调料的抽象类吧!

                   //因为我们要求调料可以和饮料本身互换,所以必须从Beverage派生

              Public abstract class CondimentDecorator extends Beverage

              {

                            //所以调料都重新实现了GetDescription方法,稍后我们会看到缘由……

                     Public abstract string GetDescription();

              }

 

Page 96

       编写具体饮料的代码

       现在把基类放在一边,我们来实现写具体饮料的代码吧!我们从Espresso(一种浓咖啡)开始。还记得吗:我们必须在具体饮料中设置相应的描述还有实现抽象基类方法Cost

                   //EspressoBeverage

              Public class Espresso extends Beverage

              {

                     Public Espresso()

                     {

                                     //我们在构造函数中设置描述,该实例变量从抽象基类中继承而来的

                            Description = “Espresso Coffee”;

                     }

                     Public double Cost()

                     {

                                     //我们无须担心附加在咖啡上的调料的价格的计算。只知道自身价格

                            Return 0.88;

                     }

              }

       你可以以相同方式实现其余三种咖啡。

 

Page 97

       为调料编写代码

       回顾一下前面的DECORATOR模式的类图,我们现在已经实现了抽象成分Beverage,具体成分如HouseBlend以及装饰品的抽象类CondimentDecorator。现在我们该实现具体调料类了。下面是Mocha的代码:

                   //Mocha是种调料,所以从CondimentDecorator派生,但是它的基类也是Beverage

              Public class Mocha extends CondimentDecorator

              {

                            //下面这个实例变量用于指向Beverage

                            //1、由该实例变量引用这个Mocha包裹的Beverage

                            //2、为了设置指向beverage的引用,我们在构造函数中通过参数传递完成

                     Beverage beverage;

                     Public Mocha(Beverage beverage)

                     {

                            This.beverage =beverage;

                     }

                     Public string GetDescription()

                     {

                                     //现在我们的描述不仅仅包括Beverage的还有加入它的调料的描述,

//比如:EspressoMocha因此,我们首先委托Decorator装饰的对象即Beverage

//(记住:添加了调料的Beverage仍然是Beverage,因为调料和Beverage可以互换)

//来获取它的描述,最后再把“Mocha”加入

                            Return beverage.GetDescription() +”,Mocha”;

                     }

                     Public double Cost()

                     {

                                     //类似方法GetDescription,我们先委托Beverage来给出它的价格,

//然后再把Mocha的价格加入

                            Return beverage.Cost() + 0.2;

                     }

              }

       下一页,我们要实例化Beverage并加入调料,但是我们首先……

练习:磨磨你的铅笔尖〉〉

    写出SoyWhip的代码,下面的测试会用到它们!

      

Page 98

       服务员,来杯咖啡

       恭喜,现在你可以坐下来点杯咖啡了,然后享受你使用DECORATOR模式实现的灵活设计是如何发挥它们的神奇作用。

       下面是点杯咖啡的代码:

              Public static void Main()

              {

                     Beverage b1 = new Espresso();

                     System.out.printl(bl.GetDescription()+”$”+b1.Cost());

 

                     Beverage b2 = new HouseBlend();//创建HouseBlend对象

                     B2 = new Mocha(b2);//Mocha封装它

                     B2 = new Mocha(b2);//Mocha封装它

                     B2 = new Whip(b2); //Whip封装它

                     System.out.printl(b2.GetDescription()+”$”+b2.Cost());

              }

       我们在后面会在FACTORYBUILDER模式中看到创建装饰对象的更好方法。注意:BUILDER模式在附录中。

       图〉〉运行结果:略

 

Page 99

       必须提及的问题

Q1:我有些小小的问题:当我在测试一个具体的Component——HouseBlend的某些动作,比如打折,一旦我使用Decorators包装了HouseBlend,我的测试代码就不工作了。

A1:当然不会正常运行了,如果你的代码依赖具体的Component类型,Decorator将会使得代码不能运行。只有按照抽象Component类型编写代码时候,这时加入Decorator代码才不会影响原有代码的运行。但是,如果你开始面向具体Component编写代码,那么你就应该重新考虑你的系统设计以及在此使用DECORATOR模式是否适合。

Q2:对于Beverage的用户来说它可以轻易的不要最外层的调料吗?比如:我要份DarkRoast,外加MochaSoyWhip。那么我可以轻易取消Whip吗?做到这一点编码麻烦吗?

A2:你肯定认为你需要使用DECORATOR模式管理更多的对象,但是这样增加了你编码时引入你所提及的这类问题的几率。尽管如此:通常情况下,我们都是使用其它模式比如FACTORYBUILDER来创建Decorator。到后面我们讲解了这些模式之后,你会看到它们创建Decorator来封装Component时候能够做到很好的封装而且不会引起如上你所谈的这类问题,(也就是说可以轻松实现你所要求的问题的解决方案。)

Q3Decorator能够了解在整个装饰链上的其它Decorator的信息吗?我想说的是,比如我想我的GetDescrption方法能够以“Mocha double Whip”的方式打印出“Whip Mocha Whip”吗?这需要我的最外层Decorator了解它包装内部的所有Decorator的信息。

A3:我们使用Decorator的目的是给它所包装的对象添加新的功能。如果你想在整个包装链中查询多个层次的Decorator的信息,这样做就背离了模式DECORATOR的真正目的。但是,做到你所要求的输出方式是可以实现的。想象一下,我们可以有个Decorator叫做CondimentPrint,它用来分析最终的输出,比如“Whip Mocha Whip”,然后输出为“Double Whip Mocha”。给你个提示:让GetDescription方法返回每个层次描述的表ArrayList会让最终的优化输出的分析更加容易。

练习:磨磨你的铅笔尖〉〉

Starbuzz又为它们的菜单中加入了‘Size’因素。这样,顾客就可以要小、中或者大杯的咖啡。StarbuzzSize作为Coffee类的基本特征,他们为Beverage基类加入了两个方法:GetSizeSetSize。此外,他们根据咖啡Size值来对调料收费:比如对于小、中和大杯咖啡,豆酱分别收费¥10、¥15和¥20

    你如何改变Decorator类来处理这些新的需求呢?

    //哈哈,简单!!

 

Page 100

       实际使用的DecoratorsJava I/O

       Java.I/O包中类的数目相当惊人。但是不要一而再再而三的仅仅惊叹“哇塞!”现在的你既然已经学习了DECORATOR模式,所以你应该在看到I/OAPI时,应该感到有些面熟,因为这个包很大程度上以DECORATOR模式构建的。下面是一系列的对象,它们采用DECORATOR模式给读取文件动态添加功能。

图〉〉略

LineNumberInputStreamBufferInputStreamFileInputStream)))àText File

FileInputStreamJavaI/O提供的Component之一,还有StringBufferInputStreamByteArrayInputStream等其它的Components。所有这些基本Component都是用来读取字节。

BufferInputStream是一个具体的Decorator,它为Component添加了两个功能:它缓存输入以提高性能,它还为读取基于字符的输入添加了一个方法ReadLine,这样就使每次读取一行。

LineNumbetInpuStream,也是个具体的Decorator,它增加读取数据时候计算行数的功能。

       这两个Decorator都是派生于FileterInputStream这个抽象类。

 

Page 101

       Java I/O类附加功能

       这个设计和Starbuzz的设计大同小异。你现在看Java.IO的文档时应该有些基础了来为多个输入流对象添加装饰品。

       输出流也有同样的设计。此外你可能也发现Reader/Writer流也有极其相似的设计(尽管有些不一致甚至矛盾的地方,但是足以让你理解设计的意图了。)

       但是Java.IO同样指出了DECORATOR模式的缺陷:使用这个模式的设计会导致大量的小类,这样会给开发者使用基于该模式的API时候制造障碍。但是现在你对DECORATOR模式已经有了一定的理解,这样有利于你理解别人基于DECORATOR模式设计而得的大量类是如何组织的,这样你就可以轻松通过包装添加功能了。

 

Page 102~103

       编写自己的Java.I/O Decorator……

       代码略

 

Page 104

       模式揭秘pattern exposed

       这个星期是DECORATOR的表白

       HeadFirst:欢迎你,DECORATOR模式,我听说你最近有些情绪低落。

DECORATOR:是的,我知道大家都认为我是个很强大的模式,但是,你知道我也像其它模式一样都有缺点的。

HeadFirst:那么你能跟我们谈谈你的麻烦呢?

DECORATOR:当然,你知道我有能力给设计带来灵活性,在很大程度上,这是事实。但是我也有缺陷。正如你看到的,我会给设计引入大量小类,这会导致其它人使用该设计的人难以理解。

HeadFirst:你能给我们举个例子吗?

DECORATOR:以Java I/O库为例吧!它们在初学者眼里难以理解是出了名的。但是如果使用者能够将那些小类都看作对InputStream的包装,这样的话会很容易理解了。
HeadFirst
:这听起来并不很糟,你依然是个非常棒的模式,改善使用者对此的理解那只是公共教育的问题,不是吗?

DECORATOR:恐怕问题不只有这些。我还有个类型问题:你可能看到有些用户会写出一些依赖具体类型的代码,然后就在没有弄明白怎么回事的情况下引入Decorator。我有个不错的特征就是:用户可以在不知道处理Decorator的情况下动态使用Decorator为已有对象添加功能。但是正如我说的,有些代码是依赖具体类型的,这个时候只要你引入Decorator进行动态地添加功能,就必然会有个炸弹“嘣”的爆炸!

HeadFirst:好的,我认为人们都应该知道在使用你的时候应该谨慎一些!我想这个也不应该是让你情绪如此低落呀?

DECORATOR:我知道,我尽量不让它影响我的情绪。但是我还有个问题就是,每当我引入Decorators对象时候,就会给实例化Component添加更多的复杂性。一旦你不仅仅要实例化Component时,此时你就必须再给它包装上不知道多少个Decorator

HeadFirst:下个星期我会采访FACTORYBUILDER模式,我听说它们能够很好解决你的这些问题。

DECORATOR:真的吗?看来我应该和这些伙计们经常聊聊了!

HeadFirst:好的,在我们眼里,你依然是个强大的模式,能够使得设计更加有柔性,并且严格遵循了OPEN-CLOSED原则。所以打起精神来,多想想积极的方面!

DECORATOR:我会尽力的,谢谢你!

 

Page 105

给你的设计工具箱里添些新的东西

现在你又读完了一章并且收获了一个新的设计原则和模式。

l       OO基础

抽象、封装、多态、继承

l       OO原则

n         封装变化

n         使用组合而不是继承

n         面向接口编程而不是实现

n         在相互影响的对象间努力做到松散耦合设计

//下面就是著名的OPEN-CLOSED原则,我们必须尽力做到让系统设计可以容易扩展

//但是不允许修改。

n         类应该支持扩展但杜绝修改

l       OO模式

n         策略STRATEGY

n         观测者OBSERVER

//这是我们第一个满足OPEN-CLOSE原则的模式。

//真是第一个吗?前面两个模式遵循这个原则吗?

n         装饰者DECORATOR

该模式可以动态地为对象附加功能。它为功能扩展提供了一种替代继承的灵活方式。(Decorators provide a flexible alternative to subclassing for extending functionally.

       关键知识点

l       继承是扩展的一种方式,但是不是获取设计柔性的最好方式。

l       我们的设计应当允许扩展行为并且不需要改动已有代码

l       组合和委托Delegate经常用来在运行时动态添加新的行为

l       DECORATOR模式为扩展功能提供了一种替代继承的更灵活的方式

l       DECORATOR模式中包含一系列Decorator类来包装具体的Component

l       Decorator类和它所包装的Component有相同的基类型(通过继承抽象类或者实现接口)

l       Decorator可以在调用Component方法之前或者/和之后通过添加新的功能来改变Component的行为,甚至替代整个Component功能

l       你可以使用任意数目的Decorator来包装Component

l       当用户代码不是依赖具体的Component类型时,Decorator的使用对用户代码是没有任何影响的。

l       该模式会给我们的设计中引入大量小型对象,过度使用会带来额外的复杂性。

      

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值