1、定义javascript
装饰者模式可用来透明地把对象包装在具备一样接口的另外一个对象之中。这样一来,你能够给一个方法添加一些行为,而后将方法调用传递给原始对象。相对于建立子类来讲,使用装饰者对象是一种更灵活的选择(装饰者提供比继承更有弹性的替代方案)。html
装饰者用于经过重载方法的形式添加新功能,该模式能够在被装饰者前面或者后面加上本身的行为以达到特定的目的。java
2、举例设计模式
2.1 装饰者是一种实现继承的替代方案。当脚本运行时,在子类中添加行为会影响原有类全部的实例,而装饰者却否则。取而代之的是它能给不一样对象各自添加新行为。架构
//须要装饰的类(函数) function Macbook() { this.cost = function () { return 1000; }; } // 装饰——添加内存条 function Memory(macbook) { this.cost = function () { return macbook.cost() + 75; }; } // 装饰——支持蓝光影片驱动 function BlurayDrive(macbook) { this.cost = function () { return macbook.cost() + 300; }; } // 装饰——添加保修 function Insurance(macbook) { this.cost = function () { return macbook.cost() + 250; }; } // 用法 var myMacbook = new Insurance(new BlurayDrive(new Memory(new Macbook()))); console.log(myMacbook.cost()); // 1000 + 75 + 300 + 250
固然,咱们也能够经过添加子类的方式,设计三个子类(MemoryMac、BlurayDriveMac、InsuranceMac),复写cost方法。可是,若是未来有另一个品牌的电脑,如dell品牌的电脑,那么,就须要另外建立三个子类,这样的设计就过于冗余复杂。app
2.2 接下来引入工厂模式的例子。上次见到AcmeBicycleShop类的时候,顾客能够购买的自行车有4种型号。后来这家商店开始为每一种自行车提供一些额外的特点配件。如今顾客再加点钱就能够买到带前灯、尾灯或铃铛的自行车。每一种可选配件都会影响到售价和车的组装方法。咱们使用装饰者模式来实现该功能。ide
首先,定义一个装饰者超类——函数
var BicycleDecorator = function(bicycle) { this.bicycle = 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(); } };
如今来添加一个装饰者类,给自行车添加头灯——工具
var HeadlightDecorator = function(bicycle) { this.bicycle = bicycle; }; HeadlightDecorator.prototype = new BicycleDecorator(); HeadlightDecorator.prototype.assemble = function() { return this.bicycle.assemble() + ' Attach headlight to handlebars'; }; HeadlightDecorator.prototype.getPrice = function() { return this.bicycle.getPrice() + 15.00; };
调用——post
// 一个普通的AcmeComfortCruiser车子 var myBicycle = new AcmeComfortCruiser(); console.log(myBicycle.getPrice()); // 399.00 // 咱们为AcmeComfortCruiser的车子添加前置灯以后的自行车售价 myBicycle = new HeadlightDecorator(acmeComfortCruiser); console.log(myBicycle.getPrice()); // 399.00 + 15.00 = 414.00
会发现这里的myBicycle变量被重置为对应的装饰者对象,也就意味着将不能再访问原来的那个自行车对象。不过,没有关系,由于这个装饰者彻底能够和自行车对象互换使用。装饰者最重要的特色之一就是它能够用来替代其组件(这里,咱们用new HeadlightDecorator(acmeComfortCruiser)替换了new AcmeComfortCruiser()对象)。这是经过确保装饰者和对应组件都实现了Bicycle接口而达到的。若是装饰者对象与其组件不能互换使用,它就是丧失了其功用。要注意防止装饰者和组件出现接口方面的差别。这种模式的好处之一就是能够透明地用新对象装饰现有的独享,而这并不会改变代码中的其余东西。只有装饰者和组件实现了一样的接口才能作到这一点。
添加尾灯的装饰者——
var TaillightDecorator = function(bicycle) { // implements Bicycle this.superclass.constructor(bicycle); // Call the superclass's constructor. } extend(TaillightDecorator, BicycleDecorator); // Extend the superclass. TaillightDecorator.prototype.assemble = function() { return this.bicycle.assemble() + ' Attach taillight to the seat post.'; }; TaillightDecorator.prototype.getPrice = function() { return this.bicycle.getPrice() + 9.00; };
应用——添加两个头灯,一个尾灯——
var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle. alert(myBicycle.getPrice()); // Returns 399.00 myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object // with a taillight. alert(myBicycle.getPrice()); // Now returns 408.00
会发现,能够为个人自行车实例,不断添加各类装饰。这样呢,也就实现了为自行车对象添加各类配件的需求。
2.3 装饰者修改其组件的方式,有——
1> 在方法以前添加
var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle. alert(myBicycle.getPrice()); // Returns 399.00 myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object // with the first headlight. myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object // with the second headlight. myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object // with a taillight. alert(myBicycle.getPrice()); // Now returns 438.00
2> 在方法以后添加 - 添加车架颜色的装饰
var FrameColorDecorator = function(bicycle, frameColor) { // implements Bicycle this.superclass.constructor(bicycle); // Call the superclass's constructor. this.frameColor = frameColor; } // extend(FrameColorDecorator, BicycleDecorator); // Extend the superclass. FrameColorDecorator.prototype.assemble = function() { return 'Paint the frame ' + this.frameColor + ' and allow it to dry. ' + this.bicycle.assemble(); }; FrameColorDecorator.prototype.getPrice = function() { return this.bicycle.getPrice() + 30.00; }; var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle. myBicycle = new FrameColorDecorator(myBicycle, 'red'); // Decorate the bicycle // object with the frame color. myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object // with the first headlight. myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object // with the second headlight. myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object // with a taillight. alert(myBicycle.assemble()); /* Returns: "Paint the frame red and allow it to dry. (Full instructions for assembling the bike itself go here) Attach headlight to handlebars. Attach headlight to handlebars. Attach taillight to the seat post." */
3> 替换方法
有时为了实现新行为必须对方法进行总体替换。在此状况下,组件方法不会被调用(或者虽然被调用但其返回值会被抛弃)。做为这种修改的一个例子,下面咱们将建立一个用来实现自行车的终生保修的装饰者。
var LifetimeWarrantyDecorator = function(bicycle) { // implements Bicycle this.superclass.constructor(bicycle); // Call the superclass's constructor. } // extend(LifetimeWarrantyDecorator, BicycleDecorator); // Extend the superclass. // 这里的维修方法再也不调用组件的repair方法 LifetimeWarrantyDecorator.prototype.repair = function() { return 'This bicycle is covered by a lifetime warranty. Please take it to ' + 'an authorized Acme Repair Center.'; }; LifetimeWarrantyDecorator.prototype.getPrice = function() { return this.bicycle.getPrice() + 199.00; };
4> 添加新方法
var BellDecorator = function(bicycle) { // implements Bicycle this.superclass.constructor(bicycle); // Call the superclass's constrcutor. } extend(BellDecorator, BicycleDecorator); // Extend the superclass. BellDecorator.prototype.assemble = function() { return this.bicycle.assemble() + ' Attach bell to handlebars.'; }; BellDecorator.prototype.getPrice = function() { return this.bicycle.getPrice() + 6.00; }; BellDecorator.prototype.ringBell = function() { return 'Bell rung.'; }; // 添加按铃 var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle. myBicycle = new BellDecorator(myBicycle); // Decorate the bicycle object // with a bell. alert(myBicycle.ringBell()); // Returns 'Bell rung.' // 可是BellDecorator必须放在最后应用,不然这个新方法将没法访问 var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle. myBicycle = new BellDecorator(myBicycle); // Decorate the bicycle object // with a bell. myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object // with a headlight. alert(myBicycle.ringBell()); // Method not found.
2.4 函数装饰者
装饰者并不局限于类。你也能够建立用来包装独立的函数和方法的装饰者。
// 将包装者的返回结果改成大写形式 function upperCaseDecorator(func) { return function() { return func.apply(this, arguments).toUpperCase(); } } function getDate() { return (new Date()).toString(); } getDateCaps = upperCaseDecorator(getDate); alert(getDate()); // Returns Wed Sep 26 2007 20:11:02 GMT-0700 (PDT) alert(getDateCaps()); // Returns WED SEP 26 2007 20:11:02 GMT-0700 (PDT)
函数装饰者在对另外一个函数的输出应用某种格式或执行某种转换这方面颇有用处。
下面演示,为对应的组件添加一个代码执行时间的装饰——
// 添加计时器 var ListBuilder = function(parent, listLength) { this.parentEl = $(parent); this.listLength = listLength; }; ListBuilder.prototype = { buildList: function() { var list = document.createElement('ol'); this.parentEl.appendChild(list); for(var i = 0; i < this.listLength; i++) { var item = document.createElement('li'); list.appendChild(item); } } }; var SimpleProfiler = function(component) { this.component = component; }; SimpleProfiler.prototype = { buildList: function() { var startTime = new Date(); this.component.buildList(); var elapsedTime = (new Date()).getTime() - startTime.getTime(); console.log('buildList: ' + elapsedTime + ' ms'); } }; /* Usage. */ var list = new ListBuilder('list-container', 5000); // Instantiate the object. list = new SimpleProfiler(list); // Wrap the object in the decorator. list.buildList(); // Creates the list and displays "buildList: 298 ms".
咱们对这个代码执行装饰器,进行进一步抽象——
var MethodProfiler = function(component) { var that = this; this.component = component; this.timers = {}; for(var key in this.component) { // Ensure that the property is a function. if(typeof this.component[key] !== 'function') { continue; } // Add the method. (function(methodName) { that[methodName] = function() { that.startTimer(methodName); var returnValue = that.component[methodName].apply(that.component, arguments); that.displayTime(methodName, that.getElapsedTime(methodName)); return returnValue; }; })(key); } }; MethodProfiler.prototype = { startTimer: function(methodName) { this.timers[methodName] = (new Date()).getTime(); }, getElapsedTime: function(methodName) { return (new Date()).getTime() - this.timers[methodName]; }, displayTime: function(methodName, time) { console.log(methodName + ': ' + time + ' ms'); } }; /* Usage. */ var list = new ListBuilder('list-container', 5000); list = new MethodProfiler(list); list.buildList('ol'); // Displays "buildList: 301 ms". list.buildList('ul'); // Displays "buildList: 287 ms". list.removeLists('ul'); // Displays "removeLists: 10 ms". list.removeLists('ol'); // Displays "removeLists: 12 ms".
这个例子出色地应用了装饰者模式。那个性能分析器彻底透明,它能够对各类对象添加功能,为此并不须要从那些对象派生子类。只是用这一个装饰者类便可垂手可得的对各类各样的对象进行装饰。
3、优点
装饰者是在运行期间为对象添加特性或指责的有力工具。在自行车商店这个例子中,经过使用装饰者,你能够动态地为自行车对象添加可选的特点配件。在只有部分对象须要这些特性的状况下装饰者模式的好处尤其突出。若是不采用这种模式,那么要想实现一样的效果必须使用大量子类。装饰者的运做过程是透明的,这就是说你能够用它包装其余对象,而后继续按以前使用那些对象的方法来使用它。
4、劣势
1> 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
2> 使用装饰者模式每每会增长架构的复杂程度。所以,在设计一个使用了装饰者模式的架构时,必需要多花点心思,确保本身的代码有良好的文档说明,而且容易理解。
5、总结
装饰者模式是为已有功能动态地添加更多功能的一种方式,把每一个要装饰的功能放在单独的函数里,而后用该函数包装所要装饰的已有函数对象,所以,当须要执行特殊行为的时候,调用代码就能够根据须要有选择地、按顺序地使用装饰功能来包装对象。优势是把类(函数)的核心职责和装饰功能区分开了。
源自:JavaScript设计模式(人民邮电出版社)——第十二章,装饰者模式