Javascript设计模式之装饰者模式详解篇


Posted in Javascript onJanuary 17, 2017

一、前言:

装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象。

装饰者模式的特点:

1. 在不改变原对象的原本结构的情况下进行功能添加。

2. 装饰对象和原对象具有相同的接口,可以使客户以与原对象相同的方式使用装饰对象。

3. 装饰对象中包含原对象的引用,即装饰对象是真正的原对象经过包装后的对象。

二、Javascript装饰者模式详解:

描述:

装饰者模式中,可以在运行时动态添加附加功能到对象中。当处理静态类时,这可能是一个挑战。在Javascript中,由于对象是可变的,因此,添加功能到对象中的过程本身并不是问题。

装饰者模式的一个比较方便的特征在于其预期行为的可定制和可配置特性。可以从仅具有一些基本功能的普通对象开始,然后从可用装饰资源池中选择需要用于增强普通对象的哪些功能,并且按照顺序进行装饰,尤其是当装饰顺序很重要的时候。

实现装饰者模式的其中一个方法是使得每个装饰者成为一个对象,并且该对象包含了应该被重载的方法。每个装饰者实际上继承了目前已经被前一个装饰者进行增强后的对象。每个装饰方法在“继承的对象”上调用了同样的方法并获取其值,此外它还继续执行了一些操作。

先上实例1:

//需要装饰的类(函数)
function Macbook() {
 this.cost = function () {
  return 1000;
 };
} 
//计算商品的包装费
function PackagingFee(macbook) {
 this.cost = function () {
  return macbook.cost() + 75;
 };
}
//计算商品的运费
function Freight(macbook) {
 this.cost = function () {
  return macbook.cost() + 300;
 };
} 
//计算商品的保险费用
function Insurance(macbook) {
 this.cost = function () {
  return macbook.cost() + 250;
 };
}
// 用法
var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
console.log(myMacbook.cost());//1625

我们简单的分析下上面的代码,上面的代码中,一共定义了四个函数(其中一个需要修饰的函数,三个用于修饰的函数)。

然后,声明一个变量myMacbook指向new出来的Insurance对象,Insurance对象的形参指向new出来的Freight对象,Freight对象的形参指向new出来的PackagingFee对象,PackagingFee对象的形参指向new出来的Macbook对象。

接下来,调用myMacbook的cost方法。从上面的分析,我们可以得出 myMacbook.cost()的值等于(Freight对象的cost方法+250),Freight对象的cost方法等于(PackagingFee对象的cost方法+300),PackagingFee对象的cost方法等于(Macbook对象的cost方法+75)。

所以最终的结果是:myMacbook.cost()的值 = 250 + (300 + (75 + 1000)) = 1625。

// 用法
var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
console.log(myMacbook.cost());//1625 
//上面的代码等价于下面拆分后的代码,或许拆分后代码你更能看出前后的逻辑性
var macbook = new Macbook();
var package = new PackagingFee(macbook);
var freight = new Freight(package);
var myMacbook = new Insurance(freight);
//当然,如果你不想声明这么多变量(macbook、package、freight),只用一个变量也是可以的
var macbook = new Macbook();
macbook = new PackagingFee(macbook);
macbook = new Freight(macbook);
var myMacbook = new Insurance(macbook);

再看看实例2:

function ConcreteClass() {
 this.performTask = function () {
  this.preTask();
  console.log('doing something');
  this.postTask();
 };
}
function AbstractDecorator(decorated) {
 this.performTask = function () {
  decorated.performTask();
 };
}
function ConcreteDecoratorClass(decorated) {
 this.base = AbstractDecorator;
 this.base(decorated);// add performTask method
 decorated.preTask = function () {
  console.log('pre-calling..');
 };
 decorated.postTask = function () {
  console.log('post-calling..');
 };
}
var concrete = new ConcreteClass();
var decorator1 = new ConcreteDecoratorClass(concrete);
decorator1.performTask();
//pre-calling..
//doing something
//post-calling..

实例2实际上和实例1是非常类似的,我们来简单分析下吧。首先,实例2中定义了三个函数,然后声明了两个变量concrete和decorator1,最后调用了decorator1的performTask方法。

粗看一眼,ConcreteDecoratorClass里面好像并没有performTask方法。我们先来分析下面的两行代码:

var concrete = new ConcreteClass(); //声明一个变量concrete指向new出来的ConcreteClass对象
var decorator1 = new ConcreteDecoratorClass(concrete); //声明一个变量decorator1指向new出来的ConcreteDecoratorClass对象,并传入变量concrete作为形参

然后,我们再来逐行分析下ConcreteDecoratorClass函数里面的代码:

this.base = AbstractDecorator; //定义一个当前对象(decorator1)的base属性,并指向函数AbstractDecorator
this.base(decorated); //调用base属性指向的函数,也就是调用AbstractDecorator函数,同时传入形参decorated,形参decorated指向new出来的ConcreteClass对象

说到这里,好像还是没有分析出ConcreteDecoratorClass函数里面有performTask方法,重点是看 "this"!

ConcreteDecoratorClass函数中的this指向new出来的ConcreteDecoratorClass对象(也就是和decorator1指向同一个对象);

AbstractDecorator函数里面的this关键是看哪个对象来调用这个函数,this就指向哪个对象(从代码 “this.base = AbstractDecorator; this.base(decorated);” 中我们可以看出是new出来的ConcreteDecoratorClass对象在调用AbstractDecorator函数),所以AbstractDecorator函数里面的this指向new出来的ConcreteDecoratorClass对象(也和decorator1指向同一个对象)。

总结下来,我们会发现,在上面的代码中,不管是ConcreteDecoratorClass函数里面的this,还是AbstractDecorator函数里面的this,都指向new出来的ConcreteDecoratorClass对象。

所以,当我们执行decorator1.performTask()时,它会继续执行匿名函数中的代码(decorated.performTask();),匿名函数中的decorated形参指向new出来的ConcreteClass对象,并执行该对象的performTask方法。

最后看看实例3:

var tree = {};
tree.decorate = function () {
 console.log('Make sure the tree won\'t fall');
}; 
tree.getDecorator = function (deco) {
 tree[deco].prototype = this;
 return new tree[deco];
}; 
tree.RedApples = function () {
 this.decorate = function () {
  this.RedApples.prototype.decorate(); // 第7步:先执行原型(这时候是Angel了)的decorate方法
  console.log('Add some red apples'); // 第8步 再输出 red
  // 将这2步作为RedApples的decorate方法
 }
};
tree.BlueApples = function () {
 this.decorate = function () {
  this.BlueApples.prototype.decorate(); // 第1步:先执行原型的decorate方法,也就是tree.decorate()
  console.log('Put on some blue apples'); // 第2步 再输出blue
  // 将这2步作为BlueApples的decorate方法
 }
}; 
tree.Angel = function () {
 this.decorate = function () {
  this.Angel.prototype.decorate(); // 第4步:先执行原型(这时候是BlueApples了)的decorate方法
  console.log('An angel on the top'); // 第5步 再输出angel
  // 将这2步作为Angel的decorate方法
 }
};
tree = tree.getDecorator('BlueApples'); // 第3步:将BlueApples对象赋给tree,这时候父原型里的getDecorator依然可用
tree = tree.getDecorator('Angel'); // 第6步:将Angel对象赋给tree,这时候父原型的父原型里的getDecorator依然可用
tree = tree.getDecorator('RedApples'); // 第9步:将RedApples对象赋给tree
tree.decorate(); // 第10步:执行RedApples对象的decorate方法
//Make sure the tree won't fall
//Add blue apples
//An angel on the top
//Put on some red apples

实例3看起来很复杂,实际上分析逻辑还是和前面两个实例一样,我们可以看出实例3中一共声明了5个函数表达式。我们重点分析下下面的代码:

//tree.getDecorator('BlueApples')返回new出来的tree.BlueApples的实例对象,并将该对象赋值给空的tree对象
tree = tree.getDecorator('BlueApples'); //new出来的tree.BlueApples的实例对象的原型指向 --> 空对象tree 
//tree.getDecorator('Angel')返回new出来的tree.Angel的实例对象(这行代码中的第二个tree已经是上面一行代码运行结果后的tree.BlueApples的实例对象)
tree = tree.getDecorator('Angel'); //new出来的tree.Angel的实例对象的原型指向 --> tree.BlueApples的实例对象
//tree.getDecorator('RedApples')返回new出来的tree.RedApples的实例对象(这行代码中的第二个tree已经是上面一行代码运行结果后的tree.Angel的实例对象)
tree = tree.getDecorator('RedApples'); //new出来的tree.RedApples的实例对象的原型指向 --> tree.Angel的实例对象
//调用tree.decorate(),这里的tree已经是new出来的tree.RedApples的实例对象了。
//tree.RedApples的实例对象的decorate属性方法里面的第一行代码是 “this.RedApples.prototype.decorate()”
//结合上面的分析可以得出以下的原型链结构:
//this.RedApples.prototype --> tree.Angel;
//tree.Angel.prototype --> tree.BlueApples;
//tree.BlueApples.prototype --> 空对象tree
tree.decorate();

分析到这里,就不难知道最后的输出结果了。

三、其他:

我们可以看出本文章中的装饰者模式案例中用了很多this,对this不太了解的朋友可以移步到 《深入理解javascript中的 “this”》。

本文案例建议复制下来逐行分析,赶紧行动起来吧!

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
js中使用DOM复制(克隆)指定节点名数据到新的XML文件中的代码
Jul 27 Javascript
node.js 一个简单的页面输出实现代码
Mar 07 Javascript
实例讲解JS中数组Array的操作方法
May 09 Javascript
深入理解JavaScript中的对象复制(Object Clone)
May 18 Javascript
AngularJS Controller作用域
Jan 09 Javascript
vue组件如何被其他项目引用
Apr 13 Javascript
JavaScript切换搜索引擎的导航网页搜索框实例代码
Jun 11 Javascript
jquery实现点击a链接,跳转之后,该a链接处显示背景色的方法
Jan 18 jQuery
vue单页缓存存在的问题及解决方案(小结)
Sep 25 Javascript
JavaScript常见鼠标事件与用法分析
Jan 03 Javascript
vue实现登录页面的验证码以及验证过程解析(面向新手)
Aug 02 Javascript
uni-app 支持多端第三方地图定位的方法
Jan 03 Javascript
微信小程序图表插件(wx-charts)实例代码
Jan 17 #Javascript
jQuery图片拖动组件Dropzone用法示例
Jan 17 #Javascript
js生成随机数方法和实例
Jan 17 #Javascript
jQuery表单插件ajaxForm实例详解
Jan 17 #Javascript
js实现手机拍照上传功能
Jan 17 #Javascript
angular实现form验证实例代码
Jan 17 #Javascript
基于jQuery实现数字滚动效果
Jan 16 #Javascript
You might like
dede3.1分页文字采集过滤规则详说(图文教程)
2007/04/03 PHP
php获取指定数量随机字符串的方法
2017/02/06 PHP
PHP中OpenSSL加密问题整理
2017/12/14 PHP
Javascript 生成指定范围数值随机数
2009/01/09 Javascript
jquery 弹出层实现代码
2009/10/30 Javascript
JavaScript 打地鼠游戏代码说明
2010/10/12 Javascript
Firefox中autocomplete="off" 设置不起作用Bug的解决方法
2011/03/25 Javascript
鼠标滚轮改变图片大小的示例代码
2013/11/20 Javascript
jQuery中的read和JavaScript中的onload函数的区别
2014/08/27 Javascript
了不起的node.js读书笔记之mongodb数据库交互
2014/12/22 Javascript
jQuery+AJAX实现遮罩层登录验证界面(附源码)
2020/09/13 Javascript
简单实现JS计算器功能
2016/12/21 Javascript
jQuery中on方法使用注意事项详解
2017/02/15 Javascript
react.js 获取真实的DOM节点实例(必看)
2017/04/17 Javascript
vue 搭建后台系统模块化开发详解
2019/05/01 Javascript
JQuery获取可视区尺寸和文档尺寸及制作悬浮菜单示例
2019/05/14 jQuery
微信小程序位置授权处理方法
2019/06/13 Javascript
[01:35:13]DOTA2-DPC中国联赛 正赛 DLG vs PHOENIX BO3 第一场 1月18日
2021/03/11 DOTA
python练习程序批量修改文件名
2014/01/16 Python
Python中还原JavaScript的escape函数编码后字符串的方法
2014/08/22 Python
Python描述器descriptor详解
2015/02/03 Python
python 安装virtualenv和virtualenvwrapper的方法
2017/01/13 Python
python 阶乘累加和的实例
2019/02/01 Python
FitFlop美国官网:英国符合人体工学的鞋类品牌
2018/10/05 全球购物
乡镇三项教育实施方案
2014/03/30 职场文书
房屋租赁协议书
2014/04/10 职场文书
导师推荐信范文
2014/05/09 职场文书
英文演讲稿
2014/05/15 职场文书
校园元旦活动总结
2014/07/09 职场文书
自我管理的活动方案
2014/08/25 职场文书
农民工预备党员思想汇报
2014/09/14 职场文书
优秀团员自我评价
2015/03/10 职场文书
六一亲子活动感想
2015/08/07 职场文书
导游词之永泰公主墓
2019/12/04 职场文书
MySQL中使用or、in与union all在查询命令下的效率对比
2021/05/26 MySQL
Golang 对es的操作实例
2022/04/20 Golang