装饰器模式(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. poj 2051 Argus(优先队列)

    题目链接: http://poj.org/problem?id=2051 思路分析: 优先级问题,使用优先队列求解:当执行某个任务后,再增加一个任务到队列中, 该任务的优先级为执行任务的时间加上其时间 ...

  2. python下线程以及锁

    1.python多线程 #encoding=utf-8 """ python多线程,并非真正意义上的多线程 全局锁:在指定时间里,有且只有一个线程在运行 "&q ...

  3. sql 和 nosql 说明

    在传统的数据库中, 数据库的格式是由表(table).行(row).字段(field)组成的.表有固定的结构,规定了每行有哪些字段,在创建时被定义,之后修改很困难.行的格式是相同的,由若干个固定的字段 ...

  4. 【转】Ubuntu 上编译Android出现cannot find -lstdc++解决办法

    [转]Ubuntu 上编译Android出现cannot find -lstdc++解决办法 在Ubuntu 12.04 x86_64机器上编译Android出现下面错误,是因为找不到32bit的li ...

  5. Setting property 'source' to 'org.eclipse.jst.jee.server [问题点数:40分]

    链接地址:http://bbs.csdn.net/topics/390131469 警告: [SetContextPropertiesRule]{Context} Setting property ' ...

  6. JavaSE学习总结第22天_IO流4

    -  22.01  数据输入输出流的概述和讲解 操作基本数据类型 public class DataInputStreamextends FilterInputStream implements Da ...

  7. BZOJ 1864: [Zjoi2006]三色二叉树( 树形dp )

    难得的ZJOI水题...DFS一遍就行了... ----------------------------------------------------------------------- #inc ...

  8. Win7/8在用账户密码登录时, 设置成保留用户名, 只输入密码

    修改注册表, 0表示保留用户名. 1表示每次都需要输入用户名密码. 位置: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersio ...

  9. eclipse导出doc文档

    选中需要导出的项目, 1 点击eclipse上面的Project,选择Generate javadoc..., 2 然后配置 javadoc command,比如我本地的路径为: C:\Program ...

  10. Oracle 字段是多个值的字符串的查询处理

    1.创建两张表一张用户表(T_User),一张兴趣小组表T_Group,其中小组成员字段存储用户ID列表以逗号隔开, 表:T_User 编号(F_ID) 名称(用户名) 1 张三 2 李四 3 王五 ...