一、定义

  装饰者模式可用来透明地把对象包装在具有同样接口的另一个对象之中。这样一来,你可以给一个方法添加一些行为,然后将方法调用传递给原始对象。相对于创建子类来说,使用装饰者对象是一种更灵活的选择(装饰者提供比继承更有弹性的替代方案)。

  装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。

二、举例

  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品牌的电脑,那么,就需要另外创建三个子类,这样的设计就过于冗余复杂。

  2.2 接下来引入工厂模式的例子。上次见到AcmeBicycleShop类的时候,顾客可以购买的自行车有4种型号。后来这家商店开始为每一种自行车提供一些额外的特色配件。现在顾客再加点钱就可以买到带前灯、尾灯或铃铛的自行车。每一种可选配件都会影响到售价和车的组装方法。我们使用装饰者模式来实现该功能。

  首先,定义一个装饰者超类——

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;
};

  调用——

// 一个普通的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".

  这个例子出色地应用了装饰者模式。那个性能分析器完全透明,它可以对各种对象添加功能,为此并不需要从那些对象派生子类。只是用这一个装饰者类即可轻而易举的对各种各样的对象进行装饰。

三、优势

  装饰者是在运行期间为对象添加特性或指责的有力工具。在自行车商店这个例子中,通过使用装饰者,你可以动态地为自行车对象添加可选的特色配件。在只有部分对象需要这些特性的情况下装饰者模式的好处尤为突出。如果不采用这种模式,那么要想实现同样的效果必须使用大量子类。装饰者的运作过程是透明的,这就是说你可以用它包装其他对象,然后继续按之前使用那些对象的方法来使用它。

四、劣势

  1> 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
  2> 使用装饰者模式往往会增加架构的复杂程度。因此,在设计一个使用了装饰者模式的架构时,必须要多花点心思,确保自己的代码有良好的文档说明,并且容易理解。

五、总结

装饰者模式是为已有功能动态地添加更多功能的一种方式,把每个要装饰的功能放在单独的函数里,然后用该函数包装所要装饰的已有函数对象,因此,当需要执行特殊行为的时候,调用代码就可以根据需要有选择地、按顺序地使用装饰功能来包装对象。优点是把类(函数)的核心职责和装饰功能区分开了。

源自:JavaScript设计模式(人民邮电出版社)——第十二章,装饰者模式

参考:深入理解JavaScript系列(29):设计模式之装饰者模式

【读书笔记】读《JavaScript设计模式》之装饰者模式的更多相关文章

  1. 再起航,我的学习笔记之JavaScript设计模式13(装饰者模式)

    装饰者模式 装饰者模式(Decorator): 在不改变原对象的基础上,通过对其进行过包装拓展(添加属性高或者方法)使原有对象可以满足用户的更复杂需求. 如果现在我们有个需求,需要做一个提交表单,当我 ...

  2. 读书笔记之 - javascript 设计模式 - 装饰者模式

    本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...

  3. 读书笔记之 - javascript 设计模式 - 命令模式

    本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的 ...

  4. 读书笔记之 - javascript 设计模式 - 代理模式

    代理(proxy)是一个对象,它可以用来控制对另一对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替本体被实例化,并使其可被远程访 ...

  5. 读书笔记之 - javascript 设计模式 - 观察者模式

    在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式是一种管理人与其任务(确切的讲,是对象及其行为和状态之间的关系)之间的关系的得力工具.用javascript的话来讲,这种模式的实 ...

  6. 再起航,我的学习笔记之JavaScript设计模式05(简单工程模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...

  7. 再起航,我的学习笔记之JavaScript设计模式06(工厂方法模式)

    上一次已经给大家介绍了简单工厂模式,相信大家对创建型设计模式有了初步的了解,本次我将给大家介绍的是工厂方法模式. 工厂方法模式 工厂方法模式(Factory Method):通过对产品类的抽象使其创建 ...

  8. 再起航,我的学习笔记之JavaScript设计模式06(抽象工厂模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...

  9. 再起航,我的学习笔记之JavaScript设计模式07(抽象工厂模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...

  10. 再起航,我的学习笔记之JavaScript设计模式05(简单工厂模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...

随机推荐

  1. UVA 1398 Meteor

    传送门 Solution: 记一颗流星在视野内的时间段为(L, R), 为了使所有(L, R)都取整数,首先将坐标放大. 放大倍数可取为 LCM(1, 2, ..., 10)= 2520 接着计算:从 ...

  2. Java初学(二)

    一.数据类型 在定义Long或者Float类型变量的时候,要加L或f(大小写无关,只是便于识别,建议不要小写L) 整数默认是int,浮点数默认是double 二.java字符 java语言采用的是Un ...

  3. Facebook内部高效工作PPT指南

    Facebook内部高效工作PPT指南 Facebook 内部分享:不论你如何富有,你都赚不到更多的时间,你也回不到过去.没有那么多的假如,只有指针滴答的时光飞逝和你应该好好把握的现在,以下25张PP ...

  4. mysql随机获取一条或者多条数据

    原文地址:http://www.im286.com/thread-7091552-1-1.html 转来备份 研究一些随机的因素,主要是讲究效率问题. 语句一: MYSQL手册里面针对RAND()的提 ...

  5. IIS短文件名泄露漏洞危害及防范方法

    危害级别:轻微 IIS短文件名泄露漏洞 WASC Threat Classification 描述: Microsoft IIS在实现上存在文件枚举漏洞,攻击者可利用此漏洞枚举网络服务器根目录中的文件 ...

  6. linux 软件安装

    A:RPM包,这种软件包就像windows的EXE安装文件一样,各种文件已经编译好,并打了包,哪个文件该放到哪个文件夹,都指定好了,安装非常方便,在图形界面里你只需要双击就能自动安装,如果在命令行模式 ...

  7. MySQL 高效分页

    create PROCEDURE USP_GetByPager( _pageindex int, _pagesize int ) BEGIN )*_pagesize; select * from A ...

  8. chrome 调试基本信息学习

    学习链接: remote-debugging-port相关: http://blog.chromium.org/2011/05/remote-debugging-with-chrome-develop ...

  9. html 的table tr td的使用

    cellspacing 属性规定单元格之间的空间. cellpadding 属性规定单元边沿与其内容之间的空白. border 简写属性在一个声明设置所有的边框属性. border 用在table属性 ...

  10. Unity3D中定时器的使用

    源地址:http://unity3d.9tech.cn/news/2014/0402/40149.html 在游戏设计过程中定时器是必不可少的工具,我们知道update方法是MonoBehavior中 ...