【读书笔记】读《JavaScript设计模式》之装饰者模式
一、定义
装饰者模式可用来透明地把对象包装在具有同样接口的另一个对象之中。这样一来,你可以给一个方法添加一些行为,然后将方法调用传递给原始对象。相对于创建子类来说,使用装饰者对象是一种更灵活的选择(装饰者提供比继承更有弹性的替代方案)。
装饰者用于通过重载方法的形式添加新功能,该模式可以在被装饰者前面或者后面加上自己的行为以达到特定的目的。
二、举例
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设计模式》之装饰者模式的更多相关文章
- 再起航,我的学习笔记之JavaScript设计模式13(装饰者模式)
装饰者模式 装饰者模式(Decorator): 在不改变原对象的基础上,通过对其进行过包装拓展(添加属性高或者方法)使原有对象可以满足用户的更复杂需求. 如果现在我们有个需求,需要做一个提交表单,当我 ...
- 读书笔记之 - javascript 设计模式 - 装饰者模式
本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...
- 读书笔记之 - javascript 设计模式 - 命令模式
本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的 ...
- 读书笔记之 - javascript 设计模式 - 代理模式
代理(proxy)是一个对象,它可以用来控制对另一对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替本体被实例化,并使其可被远程访 ...
- 读书笔记之 - javascript 设计模式 - 观察者模式
在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式是一种管理人与其任务(确切的讲,是对象及其行为和状态之间的关系)之间的关系的得力工具.用javascript的话来讲,这种模式的实 ...
- 再起航,我的学习笔记之JavaScript设计模式05(简单工程模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...
- 再起航,我的学习笔记之JavaScript设计模式06(工厂方法模式)
上一次已经给大家介绍了简单工厂模式,相信大家对创建型设计模式有了初步的了解,本次我将给大家介绍的是工厂方法模式. 工厂方法模式 工厂方法模式(Factory Method):通过对产品类的抽象使其创建 ...
- 再起航,我的学习笔记之JavaScript设计模式06(抽象工厂模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...
- 再起航,我的学习笔记之JavaScript设计模式07(抽象工厂模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...
- 再起航,我的学习笔记之JavaScript设计模式05(简单工厂模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...
随机推荐
- Java NIO、NIO.2学习笔记
相关学习资料 http://www.molotang.com/articles/903.html http://www.ibm.com/developerworks/cn/education/java ...
- JQueryEasyUI datagrid框架的基本使用
http://www.jb51.net/article/35348.htm 今天说说这个datagrid框架的基本使用,这个框架一直以来都是大家比较头疼的框架,尤其是Json数据的拼接,后台前台都很重 ...
- JS 显示时间与倒计时练习
显示时间与倒计时 HTML <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- php中文字符串翻转
转自:http://www.oschina.net/code/snippet_613962_17070 <?php header("content-type:text/html;cha ...
- php 命名空间
命名空间一个最明确的目的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误.这种情况下只要避免命名重复就可以解决,最常见的一种做法是约定一个前缀. 例:项目中有两个 ...
- SQL 基本语句
1.修改sa账户密码 在查询分析器中执行如下语句: sp_password Null,'teracypwd','sa' 把SA的密码设为"teracypwd" 执行成功后有&quo ...
- 转:Java NIO系列教程(九) Pipe
Java NIO 管道是2个线程之间的单向数据连接.Pipe有一个source通道和一个sink通道.数据会被写到sink通道,从source通道读取. 这里是Pipe原理的图示: 创建管道 通过Pi ...
- Java-java中的有符号,无符号操作以及DataInputStream
1. 无符号和有符号 计算机中用补码表示负数,并且有一定的计算方式:另外,用二进制的最高位表示符号,0表示正数.1表示负数.这种说法本身没错,可是要有一定的解释,不然它就是错的,至少不能解释,为什么字 ...
- motto7
与其羡慕别人,还不如模仿别人的过程.
- Linux命令sed
如果一个文本文件数据比较多,大概有40万条数据,我想取出第500-1000条数据,保存到另一个文件,用linux命令该如何操作? sed -n '500,1000p' 41w.txt > new ...