JS设计模式——12.装饰者模式
装饰者模式概述
本章讨论的是一种为对象添加特性的技术,她并不使用创建新子类这种手段。
装饰者模式可以用来透明的把对象包装在具有同样接口的另一个对象中。这样一来,就可以给一个方法添加一些行为,然后将方法调用传递给原始对象。
装饰者的结构
装饰者可用于为对象添加功能,她可以用来替代大量子类。
我们还是来看那个自行车的例子(第7章),假设这件商店开始为每一种自行车提供一些额外的特色配件。现在顾客再加点钱就可以买到前灯、尾灯、前挂篮等。每一种可选配件都会影响到售价和车的组装方法。我们来用装饰者模式来解决这个问题。
在这个例子中,选件类就是装饰者,而自行车类是他们的组件。装饰者对其组件进行透明包装。
包装过程如下:
step 1: 修改接口,加入getPrice方法
var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 'getPrice']);
var AcmeComfortCuiser = function(){ };
AcmeComfortCuiser.prototype = {
assemble: function(){ },
wash: function(){ },
repair: function(){ },
getPrice: function(){ }
}
step 2: 创建抽象类BicycleDecorator
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();
},
repair: function(){
return this.bicycle.repair();
},
getPrice: function(){
return this.bicycle.getPrice();
}
}
这个抽象类(装饰者类)的构造函数接受一个对象参数,并将其用作改装饰类的组件。
BicycleDecorator类是所有选件类的超类。对于那些不需要修改的方法,选件类只要从BicycleDecorator继承而来即可,而这些方法又会在组件上调用同样的方法,因此选件类对于任何客户代码都是透明的。
step 3: 创建选件类
var HeadlightDecorator = function(bicycle){
HeadlightDecorator.superclass.constructor.call(this, bicycle);
};
extend(HeadlightDecorator, BicycleDecorator);
HeadlightDecorator.prototype.getPrice = function(){
return this.bicycle.getPrice() + 15.00;
}
这个类很简单,她重新定义了需要进行装饰的方法。
step 4: 使用选件类
var myBicycle = new AcmeComfortCuiser();
console.log(myBicycle.getPrice()); // 399.00
myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice()); // 414.00
这里用来存放那个HeadlightDecorator实例的不是另外一个变量,而是用来存放自行车的同一个变量。者意味着此后将不能访问原来的那个自行车对象,不过没关系,你以后不再需要这个对象。那个装饰者完全可以和自行车对象互换使用。这也意味着你可以随心所欲的嵌套多重装饰者。
var TaillightDecorator = function(bicycle){
TaillightDecorator.superclass.constructor.call(this, bicycle);
};
extend(TaillightDecorator, BicycleDecorator);
TaillightDecorator.prototype.getPrice = function(){
return this.bicycle.getPrice() + 9.00;
}
var myBicycle = new AcmeComfortCuiser();
console.log(myBicycle.getPrice()); // 399.00
myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice()); // 414.00
myBicycle = new TaillightDecorator(myBicycle);
console.log(myBicycle.getPrice()); // 423.00
装饰者修改其组件的方式
装饰者的作用就在于以某种方式对其组件对象的行为进行修改。
在方法之后添加行为
上面提到的就是这种方法,在这里补充一下为何她能够嵌套多重装饰者。
其实那个就是一个栈。在TaillghtDecorator对象上调用getPrice方法,这将转至HeadlightDeocator上的getPrice方法,从而转至AcmeComfortcruiser对象上并返回其价格,一直到最外层上,最后的就过就是399+15+9=423.
在方法之前添加行为
var FrameColorDecorator = function(bicycle, frameColor){
FrameColorDecorator.superclass.constructor.call(this, bicycle);
this.frameColor = frameColor;
}; extend(FrameColorDecorator, BicycleDecorator);
FrameColorDecorator.prototype.assemble = function(){
return 'Paint the frame '+this.frameColor+'and allow it to dry' + this.bicycle.assemble();
}
这里通过传递参数的方法实现了在方法之前添加行为。
替换方法
这个没什么好说的,就是在重写方法的时候不调用this.bicycle.method()或者在一定条件下才调用this.bicycle.method()
添加新方法
下面我们来添加一个新方法。
var BellDecorator = function(){
BellDecorator.superclass.constructor.call(this, bicycle);
};
extend(BellDecorator, BicycleDecorator);
BellDecorator.prototype.ringBell = function(){
return 'Bell rung';
};
因为我们并没有在组件类实现这个方法,所以只有当BellDecorator是最后一个被调用的时候才可以访问到ringBell方法,如下:
var myBicycle = new AcmeComfortCuiser();
console.log(myBicycle.getPrice()); // 399.00
myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice()); // 414.00
myBicycle = new BellDecorator(myBicycle);
console.log(myBicycle.ringBell());//this is ok
myBicycle = new TaillightDecorator(myBicycle);
console.log(myBicycle.ringBell()); //this is not ok
这个问题有多个解决方案,比如在组件中定义此方法,或者设置一个过程,她可以确保如果使用了BellDecorator的话,那么他将最后被调用。但是第二种方法是有局限性的,如果我们添加了两个方法,岂不是解决不了了。
最好的方法是在BicycleDecorator的构造函数中添加一些代码,他们对组件对象进行检查,并为其拥有的每一个方法创建一个通道方法。这样以来,如果在BellDecorator外再裹上另外一个装饰者的话,内层装饰者定义的新方法仍然可以访问。具体代码如下:
var BicycleDecorator = function(bicycle){
this.bicycle = bicycle;
this.interface = Bicycle;
outerloop: for(var key in this.bicycle){
if(typeof this.bicycle[key] !== 'function'){
continue outerloop;
}
for(var i= 0, len=this.interface.methods.length; i<len; i++){
if(key === this.interface.methods[i]){
continue outerloop;
}
}
var that = this;
(function(methodName){
that[methodName] = function(){
return that.bicycle[methodName]();
};
})(key);
}
};
工厂模式的作用
下面我们用工厂模式重新改进createBicycle方法,在这里工厂模式可以统揽各种类(包括自行车类也包括装饰者类)。
var AcmeBicycleShop = function(){ };
extend(AcmeBicycleShop, BicycleShop);
AcmeBicycleShop.prototype.createBicycle = function(model, options){
var bicycle = new AcmeBicycleShop.models[model](); for(var i= 0, len=options.length; i<len; i++){
var decorator = AcmeBicycleShop.options[options[i].name];
if(typeof decorator !== 'function'){
throw new Error('Decorator '+options[i].name+' is not found');
}
var argument = options[i].arg;
bicycle = new decorator(bicycle, argument);
}
Interface.ensureImplements(bicycle, Bicycle);
return bicycle;
}; AcmeBicycleShop.models = {
'The Speedster': AcmeSpeedster,
'The Lowrider': AcmeLowrider
}; AcmeBicycleShop.options = {
'headlight': HeadlightDecorator,
'taillight': TaillightDecorator,
'bell': BellDecorator,
'color': FrameColorDecorator
};
这样一来,对象的实例化就简单多了。
函数装饰者
下面一个就创建了一个包装另外一个函数的装饰者,她的作用在于将被包装者的返回结果改为大写:
function upperCaseDecorator(func){
return func.apply(this, arguments).toUpperCase();
}
function good(){
return 'Well Done!';
}
console.log(upperCaseDecorator(good));
装饰者的使用场合
- 如果需要为类添加特性或职责,而从该类派生子类的解决办法并不实际的话,就应该使用装饰者模式。
- 如果需要为对象增添特性而又不想改变该对象的代码的话,也可以采用装饰者模式。
示例:性能分析器
我们打算在每个方法调用的前后添加一些代码,分别用于启动计时器和停止计时器并报告结果。这个装饰者必须完全透明,这样她才能应用于任何对象而又不干扰其正常的代码执行。
首先我们来创建一个测试类
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');
}
} var list = new ListBuilder('list-container', 5000);
list = new SimpleProfiler(list);
list.buildList();
对她进行通用化改造
加入现在我又为上面的例子添加removeList的方法,那么这时应该如何做呢?我们最好的选择就是对其进行改造了。
var MethodProfiler = function(component){
this.component = component;
this.times = {}; for(var key in this.component){
if(typeof this.component[key] !== 'function'){
continue;
}
}
var that = this;
(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[methodNmae] = (new Date()).getTime();
},
getElapsedTime: function(methodName){
return (new Date()).getTime() - this.times[methodName];
},
displayTime: function(methodName, time){
console.log(methodName + ': ' +time+'ms');
}
}
var list = new ListBuilder('list-container', 5000);
list = new MethodProfiler(list);
list.buildList('ol');
list.buildList('ul');
list.removeList('ol');
list.removeList('ul');
那个for...in循环逐一检查组件对象的每一个属性,跳过不是方法的属性,如果遇到方法属性,则为装饰者添加一个同命方法。这样添加的新方法中的代码会启动计时器、调用组件的同命方法、停止计时器以及返回先前保存下来的组件的同命方法的返回值。
装饰者模式之利
- 装饰者是在运行期间为对象添加特性或职责的有利工具。
- 装饰者的运作是透明的,这就是说我们可以用她包装其他对象,然后继续按之前使用那些对象的方法来使用她。
装饰者模式之弊
- 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
- 使用装饰者模式往往会增加架构的复杂程度。
JS设计模式——12.装饰者模式的更多相关文章
- js原生设计模式——12装饰者模式
1.面向对象模式装饰者 <!DOCTYPE html><html lang="en"><head> <meta charset=&q ...
- JS设计模式之装饰者模式
装饰者模式概述 在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户更复杂的需求 实际需求 在已有的代码基础上,为每个表单中的input默认输入框上边显示一行提示文案 ...
- JS 设计模式九 -- 装饰器模式
概念 装饰者(decorator)模式能够在不改变对象自身的基础上,动态的给某个对象添加额外的职责,不会影响原有接口的功能. 模拟传统面向对象语言的装饰者模式 //原始的飞机类 var Plane = ...
- 【JS设计模式】装饰者模式
装饰者模式:在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象 装饰模式的特点 (1) 装饰对象和真实对象有同样的接口.这样clien ...
- js设计模式——9.装饰器模式
装饰一个圣诞树 // 装饰器模式,让其依次执行 var tree = {}; tree.decorate = function() { console.log('Make sure the tree ...
- Java 设计模式泛谈&装饰者模式和单例模式
设计模式(Design Pattern) 1.是一套被反复使用.多人知晓的,经过分类编目 的 代码设计经验总结.使用设计模式是为了可重用代码,让代码更容易维护以及扩展. 2.简单的讲:所谓模式就是得到 ...
- C#设计模式(9)——装饰者模式(Decorator Pattern)
一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...
- 设计模式之装饰者模式-java实例
设计模式之装饰者模式 需求场景 我们有了别人提供的产品,但是别人提供的产品对我们来说还不够完善,我们需要对这个产品的功能进行补强,此时可以考虑使用装饰者模式. 我们已经有了产品,而且这个产品的功能非常 ...
- 学习javascript设计模式之装饰者模式
1.装饰者模式定义:给对象动态添加职责的方式称为装饰者(decorator)模式. js如何实现装饰者模式 通过保存原函数引用方式改写某函数 window.onload = function(){al ...
随机推荐
- HashMap,HashTable,concurrentHashMap,LinkedHashMap 区别
HashMap 不是线程安全的 HashTable,concurrentHashMap 是线程安全 HashTable 底层是所有方法都加有锁(synchronized) 所以操作起来效率会低 con ...
- js & get recursive ids
js & get recursive ids len = 0; bug for(let i = 0; i < 3; i++) { console.log(`i =`, i); let y ...
- 第218天:Angular---模块和控制器
1.使用NG实现双边数据绑定 所有需要ng管理的代码必须被包裹在一个有ng-app指令的元素中ng-app是ng的入口,表示当前元素的所有指令都会被angular管理(对每一个指令进行分析和操作) & ...
- Python学习---列表,元组,字典
### 列表 list = [1,2,3,4,5,6] list.append(7) print(list) ===>>> [1, 2, 3, 4, 5, 6, 7] list[2] ...
- tomcat设置虚拟路径映射服务器指定的物理路径
在tomcat的server.xml中的host标签中加入如下标签: <Context crossContext="false" debug="1" do ...
- BZOJ3244 NOI2013树的计数(概率期望)
容易发现的一点是如果确定了每一层有哪些点,树的形态就确定了.问题变为划分bfs序. 考虑怎样划分是合法的.同一层的点在bfs序中出现顺序与dfs序中相同.对于dfs序中相邻两点依次设为x和y,y至多在 ...
- HotSpot垃圾收集器GC的种类
堆内存的结构:
- 【hihocoder编程练习赛9】闰秒
题目链接 #include<stdio.h> #include<string.h> #include<algorithm> #include<math.h&g ...
- Android Room使用详解
使用Room将数据保存在本地数据库 Room提供了SQLite之上的一层抽象, 既允许流畅地访问数据库, 也充分利用了SQLite. 处理大量结构化数据的应用, 能从在本地持久化数据中极大受益. 最常 ...
- 【agc001d】Arrays and Palindrome
Portal -->agc001D Description 给你一个\(m\)个数的排列\(A\),这个\(A\)中元素的顺序可以随便调换,\(A\)中的元素的和为\(n\),现在要你构造一个数 ...