装饰者模式
本节讨论的是一种为对象增添特性的技术,它并不使用创建新子类的手段。装饰者模式可以用来透明地把对象包装在具有同样接口的另一对象之中。这样一来,你可以给一个方法添加一些行为,然后将方法调用传递给原始对象。相对于创建子类来说,使用装饰都对象是一种更灵活的选择。这种模式特别适合javascript,因为通常javascript代码并不怎么依赖对象的类型。
装饰者的结构
装饰者可用于为对象增加功能。它可以用来替代大量子类。为了准确说明这个概念,我们将进一步分析前面介绍的那个自行车商店的例子。你上次见到AcmeBicycleShop类的时候,顾客可以购买的自行车有4种型号。后来这家商店开始为每一种自行车提供一些额外的特色配件。现在顾客再加点钱就可以买到带前灯、尾灯、前挂货篮或铃铛的自行车。每一种可选配件都会影响到售价和组装方法。这个问题最基本的解决办法是为选件的每一种组合创建一个子类:
var AcmeComfortCruiser = function(){…}
var AcmeComfortCruiserWithHeadlight = function(){…}
var AcmeComfortCruiserWithTaillight = function(){…}
var AcmeComfortCruiserWithHeadlightAndTaillight = function(){…}
var AcmeComfortCruiserWithBasket = function(){…}
var AcmeComfortCruiserWithHeadlightAndBasket = function(){…}
var AcmeComfortCruiserWithTaillightAndBasket = function(){…}
var AcmeComfortCruiserWithHeadTaillightAndBasket = function(){…}
…
但是这种办法根本行不通,原因很简单:这需要实现100多个类。而且这样做的话你不得不对工厂方法进行修改,以便能创建分别属于这100个子类的自行车卖给顾客。你恐怕不想让自己的余生都花在维护成百多个子类上,所以,得想点更好的办法才是。
装饰者模式对于实现这些选择再合适不过了。你不用为自行车和选件的每一种组合创建一个子类,而只需创建4个新类(一个类针对一种选件)即可。这些类与那4种自行车类一样都实现Bicycle接口,但它们只被用作这些自行类的包装类。在这些选件类上进行的方法调用将被转到它们的包装的自行车类上,有时会稍有修改。在这例子中,选件类就是装饰者,而自行车类是它们的组件。装饰者对其组件进行了透明包装,二者可以互换使用,这是因为它们实现了同样的接口。下面我们来看应该怎样实现自行车装饰者类。首先要改一下接口,加入一个getPrice方法
var Bicycle = new Interface(‘Bicycle’,[‘assemble’,’wash’,’ride’,’repair’,’getPrice’]);
所有自行车类和选件装饰者都要实现这个接口。
AcmeComfortCruiser类大致是这个样子:
var AcmeComfortCruiser = functiion(){
//implements Bicycle
}
AcmeComfortCruiser.prototype= {
assemble:function(){
//…
},
wash:function(){
//…
},
ride:function(){
//…
},
repair:function(){
//…
},
getPrice:function(){
//…
return 399.00;
}
}
为了简化任务,也为了方便以后增添更多选件,我们将创建一个抽象类BicycleDecorator,所有选件类都从此派生。它提供了Bicycle接口所要求的各个方法的默认版本:
/*TheBicycleDecorator abstractor class*/
var BicycleDecorator = function(bicycle){
Interface.ensureImplements(bicycle,Bicycle);
this.bicycle = bicycle;
}
BicycleDecorator.prototype= {
assemble:function(){
return this.bicycle.assemble();
},
wash:function(){
return this.bicycle. wash();
},
ride:function(){
return this.bicycle. ride();
},
repair:function(){
return this.bicycle. repair();
},
getPrice:function(){
return this.bicycle. getPrice();
}
}
几乎没有比这更简单的装饰者类了。它的构造函数接受一个对象参数,并将其用作该装饰者的组件。该类实现了Bicycle接口,它所实现的第一个方法所做的只是在其组件上调用同名方法。乍一看这与组合模式的工作方式非常相似。对于那些不需要修改的方法,选件类只要使用从BicycleDecorator继承而来的版本即可,而这些方法又会在组件上调用同样的方法,因此选件类对于任何客户代码都是透明的。到了这里,装饰者开始变得有趣了。有了BicycleDecorator,创建各种选件类很容易,只需要调用超类的的构造函数并改写某些方法即可。
/*HeadlightDecoratorclass*/
var HeadlightDecorator=function(bicycle){
//call the superclass’s constructor
HeadlightDecorator.superclass.constructor.call(this,bicycle);
extend(HeadlightDecorator,BicycleDecorator);
HeadlightDecorator.prototype.assemble = function(){
return this.bicycle.assemble()+”Attachheadlight to handlebars. ‘;
}
HeadlightDecorator.prototype.getPrice = function(){
return this.bicycle.getPrice()+15.00;
}
这个类很简单。它重定义了需要进行装饰的两个方法。本例中装饰这些方法的做法是,先执行组件的方法,然后在此基础上附加一些装饰元素。assemble方法中附加的是一条指示,而getPrice方法则是把前灯的价格计入总价格当中。
现在一切就绪,该来看看怎么使用装饰者了。要创建一辆带前灯的自行车,首先应该创建自行车的实例,然后以该自行车对象为参数实例化前灯选件。在此之后,应该只使用这个HeadlightDecorator对象,你完全可以将其视为一辆自行车,而把它是一个装饰者对象的这件事抛在脑后:
var myBicycle =new AcmeComfortCruiser();
alert(myBicycle.getPrice());//399.00
myBicycle = newHeadlightDecorator(myBicycle); //Decorate the bicycle object.
alert(myBicycle.getPrice());//Now the bicycle object.
如上所示,其它组件类也这样实现
接口在装饰者模式中的角色
装饰者模式颇多利益于接口的使用。装饰者最重要的特点之一就是它可以用来替代其组件。接中在此发挥着两个方面的作用。首先,它说明了装饰者必须实现哪些方法,这有助于防止开发过程中的错误。通过创建一个具有一批固定方法的接口,你所面对的就不再是一个游移不定的目标。此外,它还可以在新版工厂中用来确保所创建的对象都实现了必需的方法。如果装饰者对象与其组件不能互换使用,它就丧失了其功用。这是装饰者的关键特点。要注意防止装饰者和组件出现接口方面的差异。这种模式的好处之一就是可以透明地用新对象装饰现有系统中的对象,而并不会改变代码中的其他东西。只有装饰和组件实现了同样的接口才能做到这一点。
装饰者模式和组合模式的比较
组合模式是一种结构模式,用于把众多子对象组织为一个整体。藉此程序与大批对象打交道时可以将它们当作一个对象来对待,并将它们组织为层次性的树。通常它并不修改方法调用,而只是将其沿组合对象与子对象的链向下传递,直到到达并落实在叶对象上。
装饰者模式也是一种结构型模式,但它并非用于组织对象,而是用于不修改现有对象或从其派生子类的前提下为其增加职责。在一些比较 简单的例子中,装饰者会透明而不加修改地传递所有方法调用,不过创建装饰者的目的就在于对方法进行修改。
装饰者修改其组件的方式
装饰者的作用在于以某种方式 对其组件对象的行为进行修改:
在方法之后添加行为
先调用组件的方法,并在其返回后实施一些附加的行为。
如下将创建一辆带有两个前灯和一个尾灯的自行车:
var myBicycle =new AcmeComfortCruiser(); //instance the bicycle.
alert(myBicycle.getPrice());// returns 399.00
myBicycle = new HeadlightDecorator(myBicycle);
myBicycle = newTaillightDecorator(myBicycle);
alert(myBicycle.getPrice());//now returns 438.00
在方法之前添加行为
如果行为修改发生在执行组件方法之前,那么要么必须把装饰者行为安排在调用组件方法之前,要么必须设法修改传递组件方法的参数值。下面提供例子实现一个提供车架颜色选择的装饰者:
var FrameColorDecorator = function(bicycle,frameColor){
FrameColorDecorator.superclass.constructor.call(this,bicycle);
this.frameColor =fameColor;
}
extend(FrameColorDecorator,BicycleDecorator);
FrameColorDecorator.prototype.getPrice= function(){
return this.bicycle.getPrice()+30.00;
};
FrameColorDecorator.prototype.assemble=function(){
return “Paint the frame”+this.frameColor+”andallowit to dry. “+this.bicycle.assemble();
上例中新加一个frameColor状态且本例中的assemble方法添加的步骤出现在其他组装指示之前而不是之后。
替换方法
有时为了实现新行为必须对方法进行整体替换。在此情况下,组件方法不会被调用。下面我来实现一个用来实现自行车终生保修的装饰者:
var LifetimeWarrantyDecorator = function(bicycle){
LifetimeWarrantyDecorator.superclass.constrcutor.call(this,bicycle);
}
extend(LifetimeWarrantyDecorator,BicycleDecorator);
LifetimeWarrantyDecorator.prototype.repair= function(){
return “this bicycle is covered by aliftetime warranty. please take it to an authorized Acme Repair Center”
}
LifetimeWarrantyDecorator.prototype.getPrice= function(){
return this.bicycle.getPrice()+199.00;
}
添加新方法
给自行车添加一个按铃方法
var BellDecorator= function(bicycle){
BellDecorator.superclass.constructor.call(this,bicycle);
}
extend(BellDecorator,BicycleDecorator);
BellDecorator.prototype.assemble=function(){
return this.bicycle.assemble()+”Attachbell to handlebars.”;
}
BellDecorator.prototype.getPrice=function(){
return this.bicycle.getPrice();+6.00;
}
BellDecorator.prototype.ringBell= function(){
return “Bell rung”;
}