装饰器模式(Decorator)

在装饰器模式中,可以在运行时给一个对象动态的添加额外的功能。当和静态类打交道的时候(static classes),这可能是一个挑战。但在JavaScript中,对象是可变的,所以给对象添加额外功能的的过程在JavaScript中本身就不是问题。
装饰器一个方便的功能就是期望行为(expected behavior)的定制和配置。你从你的简单对象开始,只有一些基础功能。然后你从一个可访问的包装器池中挑出那些你想用来增强你的简单对象的包装器并按一定顺序装饰,如果顺序是重要的。

用法(Usage)

让我们看一下这个模式的一个示例用法。假设你正在致力于一个卖东西的web程序。每笔新的销售(new sale)记录是一个sale对象。这个sale“知道”这个条目(item)的价格并可以通过调用sale.getPrice()方法返回。取决于这个情况,你可以开始用额外的功能装饰这个对象。想象一下这个场景,销售的顾客在加拿大魁北克省(加拿大魁北克省)。这种情况下,购买者需要支付联邦税(federal tax)和魁北克省税(provincial Québec tax)。遵循装饰器模式,你将会说你用一个联邦税装饰器和一个魁北克省税装饰器“装饰”这个对象。你也可以用一个价格格式化功能装饰这个对象。这个场景可能看起来像下面这样:
var sale = new Sale(100); // the price is 100 dollars
sale = sale.decorate('fedtax'); // add federal tax
sale = sale.decorate('quebec'); // add provincial tax
sale = sale.decorate('money'); // format like money
sale.getPrice(); // "$112.88"

在其它场景中,购买者可能在一个没有省税的省,并且你也可能想用加币(Canadian dollars)格式化价格,那么你可以这样做:

var sale = new Sale(100); // the price is 100 dollars
sale = sale.decorate('fedtax'); // add federal tax
sale = sale.decorate('cdn'); // format using CDN
sale.getPrice(); // "CDN$ 105.00"

正如你看到的,这是一种在运行时灵活添加功能和调整对象的方法。让我们看一下如何实现这种模式。

实现(Implementation)

实现装饰器模式的一个方法是每个装饰器都是一个对象包含了应该被重写的方法。实际上每个装饰器都继承至前一个装饰器装饰之后的增强对象。每个被装饰的方法将调用在uber(继承的对象)上相同的方法且获取值并额外做一些处理。

最终的影响就是当你在第一个示例中调用sale.getPrice(),你正在调用money装饰器的方法(见图7-1),因为每个被装饰的方法首先调用parent的方法,moneygetPrice()首先调用
quebecgetPrice()方法,这个方法再轮流调用fedtaxgetPrice()等。这条链一直到最初的没包装的Sale()构造方法实现的getPrice()方法。

这是从构造方法和原型开始的方法:
function Sale(price) {
this.price = price || 100;
}
Sale.prototype.getPrice = function() {
return this.price;
};

所有将被实现的装饰器对象将被作为构造方法属性的属性:

Sale.decorators = {};

让我们看一个装饰器的例子。它是一个实现了自定义的getPrice()方法的对象。记住这个方法首先从parent方法获得值然后修改那个值:

Sale.decorators.fedtax = {
getPrice: function() {
var price = this.uber.getPrice();
price += price * 5 / 100;
return price;
}
};

类似的我们可以实现其它的装饰器,需要多少实现多少。它们可以作为核心Sale()的功能扩展,像插件一样实现(implemented like plugins)。它们甚至可以“存活”在额外的文件中,可以被第三方开发者开发和共享:

Sale.decorators.quebec = {
getPrice: function() {
var price = this.uber.getPrice();
price += price * 7.5 / 100;
return price;
}
};
Sale.decorators.money = {
getPrice: function() {
return "$" + this.uber.getPrice().toFixed(2);
}
};
Sale.decorators.cdn = {
getPrice: function() {
return "CDN$ " + this.uber.getPrice().toFixed(2);
}
};

最后让我们看一下“神奇(magic)”的decorate()方法,它将所有部分都连接在一起。

记住它将会像这样被调用:
sale = sale.decorate('fedtax');

这个"fedtax"字符串将相当于Sale.decorators.fedtax实现的对象。新的被装饰的对象newobj将会继承我们现有的对象(最初的对象,或在上一个装饰器添加之后的对象),就是this对象。为了做到继承这部分,让我们使用前面提到过的临时构造函数模式。我们也设置newobjuber属性,所以child可以访问到parent。然后从装饰器复制所有的其它属性到新的被装饰的对象newobj。最后newobj被返回,在我们的示例中,它将会成为新的被更新的sale对象:

Sale.prototype.decorate = function(decorator) {
var F = function() {},
overrides = this.constructor.decorators[decorator],
i,
newobj;
F.prototype = this;
newobj = new F();
newobj.uber = F.prototype;
for (i in overrides) {
if (overrides.hasOwnProperty(i)) {
newobj[i] = overrides[i];
}
}
return newobj;
};

JavaScript学习笔记(四十四) 装饰器的更多相关文章

  1. JavaScript学习笔记(十四)——对象

    在学习廖雪峰前辈的JavaScript教程中,遇到了一些需要注意的点,因此作为学习笔记列出来,提醒自己注意! 如果大家有需要,欢迎访问前辈的博客https://www.liaoxuefeng.com/ ...

  2. Python学习笔记(十四)

    Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...

  3. VSTO学习笔记(十四)Excel数据透视表与PowerPivot

    原文:VSTO学习笔记(十四)Excel数据透视表与PowerPivot 近期公司内部在做一种通用查询报表,方便人力资源分析.统计数据.由于之前公司系统中有一个类似的查询使用Excel数据透视表完成的 ...

  4. python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法

    python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法window安装redis,下载Redis的压缩包https://git ...

  5. 如鹏网学习笔记(十四)ASP.NET

    Asp.net笔记 一.Socket类 进行网络编程的类,可以在两台计算机之间进行网络通讯 过程: 向服务器发送指令: GET /index.html HTTP/1.1 Host:127.0.0.1: ...

  6. 《机器学习实战》学习笔记第十四章 —— 利用SVD简化数据

    相关博客: 吴恩达机器学习笔记(八) —— 降维与主成分分析法(PCA) <机器学习实战>学习笔记第十三章 —— 利用PCA来简化数据 奇异值分解(SVD)原理与在降维中的应用 机器学习( ...

  7. (C/C++学习笔记) 二十四. 知识补充

    二十四. 知识补充 ● 子类调用父类构造函数 ※ 为什么子类要调用父类的构造函数? 因为子类继承父类,会继承到父类中的数据,所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程. ...

  8. Android学习笔记(十四)——自定义广播

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 我们除了可以通过广播接收器来接收系统广播, 还可以在应用程序中发送自定义的广播.下面我们来分别试一试发送自定义 ...

  9. Dynamic CRM 2013学习笔记(十四)复制/克隆记录

    经常有这样的需求,一个单据上有太多要填写的内容,有时还关联多个子单据,客户不想一个一个地填写,他们想从已有的单据上复制数据,克隆成一条新的记录.本文将介绍如何克隆一条记录,包括它的子单据以生成一条新的 ...

  10. Java框架spring 学习笔记(十四):注解aop操作

    回见Java框架spring Boot学习笔记(十三):aop实例操作,这里介绍注解aop操作 首先编写一个切入点HelloWorld.java package com.example.spring; ...

随机推荐

  1. 宣布发布 Windows Azure ExpressRoute,宣告与 Level 3 建立全新的合作伙伴关系并推出关于其他 Azure 服务令人振奋的更新

     在我们与世界各地的客户和合作伙伴交谈时,总会听到他们说,希望找到一个提供商帮助他们最大限度地发挥内部部署投资的作用并且能够利用云的灵活性.这是我们构建混合云策略和云操作系统愿景的基本原则.本着我 ...

  2. HDU 3466 Proud Merchants

    题目大意:现在给出商品,有三个参数,记为pi,qi,vi,vi是商品的在你心里价值,pi是商品的价格,qi是你要买商品的时候至少需要的钱然后求可得的最大价值. 单词积累:Merchants 商人  t ...

  3. Libev学习笔记3

    设置完需要监听的事件之后,就开始event loop了.在Libev中,该工作由ev_run函数完成.它的大致流程如下: int ev_run (EV_P_ int flags) { do { /* ...

  4. JAVA面试中的几个重要基础问题

    1.java是否会出现内存溢出?如何解决? 内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存.为了解决Java中内存溢出问题,我们首先必 ...

  5. Flex XML搜索、匹配

    - <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx=" ...

  6. mysqlcluster笔记

    1.config的datamemory和indexmemory规定的内存占有量会在ndb启动时直接占用掉,所以在计算内存时,这两个加起来要小于ndb的内存,这两个还只是数据和索引的内存,查询或者插入时 ...

  7. ASP.NET页面传值的几种方式

    页面传值是学习asp.net初期都会面临的一个问题,总的来说有页面传值. 存储对象传值.ajax.类.model.表单等!下面欧柏泰克和大家一起来看看asp.net页面传值方式一般有哪些?常用的较简单 ...

  8. Lamd表达式

    1. 普通绑定: public void button1_Click(object sender, EventArgs e) { MessageBox.Show("ok"); } ...

  9. Enze Second day

    哈喽,很高兴在云和学院又学了一天的新知识,现在,我来继续总结一下今天所学的以及对昨天的一些补充. 变量 • 声明变量的语法格式: –数据类型  变量名; •赋值:     变量名=值; 变量的命名 • ...

  10. linux shell: 搜索字符串,剔除包含特定字符的行

    搜索代码中出现CONFIG_BOOTARGS的行,但又不希望有#define和#undef, 暂时不知带如何直接用一个语句做到,因此用了如下三个语句. find . -type f | xargs g ...