装饰者模式概述

本章讨论的是一种为对象添加特性的技术,她并不使用创建新子类这种手段。

装饰者模式可以用来透明的把对象包装在具有同样接口的另一个对象中。这样一来,就可以给一个方法添加一些行为,然后将方法调用传递给原始对象。

装饰者的结构

装饰者可用于为对象添加功能,她可以用来替代大量子类。

我们还是来看那个自行车的例子(第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));

装饰者的使用场合

  1. 如果需要为类添加特性或职责,而从该类派生子类的解决办法并不实际的话,就应该使用装饰者模式。
  2. 如果需要为对象增添特性而又不想改变该对象的代码的话,也可以采用装饰者模式。

示例:性能分析器

我们打算在每个方法调用的前后添加一些代码,分别用于启动计时器和停止计时器并报告结果。这个装饰者必须完全透明,这样她才能应用于任何对象而又不干扰其正常的代码执行。

首先我们来创建一个测试类

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循环逐一检查组件对象的每一个属性,跳过不是方法的属性,如果遇到方法属性,则为装饰者添加一个同命方法。这样添加的新方法中的代码会启动计时器、调用组件的同命方法、停止计时器以及返回先前保存下来的组件的同命方法的返回值。

装饰者模式之利

  1. 装饰者是在运行期间为对象添加特性或职责的有利工具。
  2. 装饰者的运作是透明的,这就是说我们可以用她包装其他对象,然后继续按之前使用那些对象的方法来使用她。

装饰者模式之弊

  1. 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
  2. 使用装饰者模式往往会增加架构的复杂程度。

转载于:https://www.cnblogs.com/JChen666/p/3663111.html

JS设计模式——12.装饰者模式相关推荐

  1. 设计模式学习----装饰器模式

    这两天本来是自在学习java collection Framework的Fail Fast底层机制,看到核心的部分时,突然意识到设计模式的问题,上大学到现在我还没有真正理解过设计模式的概念,于是用了大 ...

  2. javascript设计模式之装饰器模式(结构型模式)

    javascript设计模式之装饰器模式 js的设计模式分为创建型模式,结构型模式和行为模式 结构模式描述了如何组合对象以提供新的功能. 装饰器模式是一种常见的结构型模式,我们可以以一个基础对象为基础 ...

  3. Java设计模式(装饰者模式-组合模式-外观模式-享元模式)

    Java设计模式Ⅳ 1.装饰者模式 1.1 装饰者模式概述 1.2 代码理解 2.组合模式 2.1 组合模式概述 2.2 代码理解 3.外观模式 3.1 外观模式概述 3.2 代码理解 4.享元模式 ...

  4. 前端也要学系列:设计模式之装饰者模式

    什么是装饰者模式 今天我们来讲另外一个非常实用的设计模式:装饰者模式.这个名字听上去有些莫名其妙,不着急,我们先来记住它的一个别名:包装器模式. 我们记着这两个名字来开始今天的文章. 首先还是上< ...

  5. 设计模式 之 装饰者模式

    2019独角兽企业重金招聘Python工程师标准>>> 设计模式 之 装饰者模式 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对 ...

  6. 【设计模式】装饰者模式 ( 概念 | 适用场景 | 优缺点 | 与继承对比 | 定义流程 | 运行机制 | 案例分析 )

    文章目录 I . 装饰者模式概念 II . 装饰者模式适用场景 III . 装饰者模式优缺点 IV . 装饰者模式与继承对比 V . 装饰者模式相关设计模式 VI . 装饰者模式四个相关类 VII . ...

  7. 【设计模式】装饰器模式的使用

    问题来源 我们在进行软件系统设计的时候,有一些业务(如下图,一些通用的非功能性需求)是多个模块都需要的,是跨越模块的.把它们放到什么地方呢? 最简单的办法就是把这些通用模块的接口写好,让程序员在实现业 ...

  8. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  9. go设计模式之装饰器模式

    go设计模式之装饰器模式 再写这篇文章时,我已经看了很多其他人发表的类似文章,大概看了这么多吧. 亓斌的设计模式-装饰者模式(Go语言描述) jeanphorn的Golang设计模式之装饰模式 七八月 ...

最新文章

  1. 使用ListView实现汽泡短信聊天
  2. java ehcahce刷新_springboot结合ehcache防止恶意刷新请求的实现
  3. matlab 如何读数据文件,详解如何在python中读写和存储matlab的数据文件(*.mat)
  4. List集合和set集合
  5. linux 串口内核加载,linux对串口编程的详解(从应用层到内核驱动,包括232,485)
  6. 9. HTML DOM getElementsByName() 方法
  7. 关于Lazarus下PowerPDF控件的使用
  8. CAXA图文档2007服务器端,CAXA图文档客户端系统管理员操作.doc
  9. 用友u8怎么导出凭证_老师,用友U8里的凭证如何导出(导出有借贷方向的)?...
  10. NTC与PTC压敏电阻在电源电路中起的作用
  11. 任正非:战略思想家的典范
  12. Java随笔记 - TCP通信的基本过程,三次握手,四次挥手
  13. Ext.js 自定义桌面注意
  14. eagleeye_EagleEye简介:户外视频监控分析和面部识别软件
  15. 振荡次数计算机控制系统,计算机控制第四章.ppt
  16. 基于微信小程序付费自习室系统(微信小程序毕业设计)
  17. Linux服务器硬盘故障后恢复数据的方法和数据恢复过程
  18. 软件测试工程师基础类面试题及参考答案
  19. git 忽视修改过的文件
  20. Linux系统管理16:shell

热门文章

  1. [转]关于Python里的类型注解
  2. 东航期货模拟交易brockerid(期货公司的客户号)
  3. 东航期货行情接口和交易接口(20190509)
  4. 数据可视化必备的高逼格图表特效,学会只需要五分钟
  5. 如何理解DT将是未来IT的转型之路?
  6. php如何生成伪静态url,thinkphp控制器(三) 伪静态及URL生成
  7. java html转换xml文件,使用Java在HTML中转换XML + XSL
  8. html 音乐能连续播放吗,音乐在不同HTML页面的连续播放问题
  9. 综合布线系统就是连接计算机等终端的什么,综合布线系统安装的方法是什么?哪位清楚?...
  10. mysql 空位补0_MySQL 删除数据后物理空间未释放