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 相关文章推荐
Flash+XML滚动新闻代码 无图片 附源码下载
Nov 22 Javascript
JQuery EasyUI 对话框的使用方法
Oct 24 Javascript
javascript利用apply和arguments复用方法
Nov 25 Javascript
AngularJS中的表单简单入门
Jul 28 Javascript
jQuery事件绑定用法详解
Sep 08 Javascript
JS中使用mailto实现将用户在网页中输入的内容传递到本地邮件客户端
Oct 08 Javascript
在点击div中的p时,如何阻止事件冒泡
Feb 07 Javascript
jQuery实现简单的滑动导航代码(移动端)
May 22 jQuery
Vue2.0实现组件之间数据交互和通信操作示例
May 16 Javascript
Vue指令之 v-cloak、v-text、v-html实例详解
Aug 08 Javascript
浅谈监听单选框radio改变事件(和layui中单选按钮改变事件)
Sep 10 Javascript
JavaScript中的null和undefined用法解析
Sep 30 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
BBS(php & mysql)完整版(四)
2006/10/09 PHP
杏林同学录(二)
2006/10/09 PHP
简单示例AJAX结合PHP代码实现登录效果代码
2008/07/25 PHP
PHP变量赋值、代入给JavaScript中的变量
2015/06/29 PHP
php实现中文转数字
2016/02/18 PHP
PHP处理Ajax请求与Ajax跨域问题
2017/02/13 PHP
php5.x禁用eval的操作方法
2018/10/19 PHP
Referer原理与图片防盗链实现方法详解
2019/07/03 PHP
将HTML自动转为JS代码
2006/06/26 Javascript
jQuery为iframe的body添加click事件的实现代码
2011/04/07 Javascript
js替代copy(示例代码)
2013/11/27 Javascript
jQuery中选择器小问题(新人难免遇到)
2014/03/31 Javascript
JavaScript职责链模式概述
2016/09/17 Javascript
JS克隆,属性,数组,对象,函数实例分析
2016/11/26 Javascript
jquery仿ps颜色拾取功能
2017/03/08 Javascript
浅谈Vue网络请求之interceptors实际应用
2018/02/28 Javascript
angular 组件通信的几种实现方式
2018/07/13 Javascript
python模拟登陆阿里妈妈生成商品推广链接
2014/04/03 Python
对python中大文件的导入与导出方法详解
2018/12/28 Python
详解Python的三种可变参数
2019/05/08 Python
python 的 scapy库,实现网卡收发包的例子
2019/07/23 Python
python多线程实现同时执行两个while循环的操作
2020/05/02 Python
详细分析Python垃圾回收机制
2020/07/01 Python
分享PyCharm最新激活码(真永久激活方法)不用每月找安装参数或最新激活码了
2020/12/27 Python
python读取excel数据并且画图的实现示例
2021/02/08 Python
台湾百利市购物中心:e-Payless
2017/08/16 全球购物
YSL圣罗兰美妆俄罗斯官网:Yves Saint Lauret RU
2020/09/23 全球购物
怎样创建、运行java程序
2014/08/01 面试题
生产车间实习自我鉴定
2013/09/23 职场文书
遗嘱公证书标准样本
2014/04/08 职场文书
导游欢迎词范文
2015/01/23 职场文书
2015年人事工作总结范文
2015/04/09 职场文书
管理失职检讨书范文
2015/05/05 职场文书
五一晚会主持词
2015/07/01 职场文书
解决numpy数组互换两行及赋值的问题
2021/04/17 Python
俄罗斯十大城市人口排名,第三首都仅排第六,第二是北方首都
2022/03/20 杂记