装饰器模式(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. Java GUI图形界面开发工具

    Applet 应用程序     一种可以在 Web 浏览器中执行的小程序,扩展了浏览器中的网页功能. 缺: 1.需要下载 Applet 及其相关文件 2.Applet 的功能是受限制的 优: 3.无需 ...

  2. Eclipse创建新项目时无法输入项目名的解决方法

    放假耍了那么久,也是该收心忙活了. 今天打开Eclipse新建项目时,发生了一个很奇怪的情况,就是在下面这个位置的输入框无法输入. 经过百度之后,发现解决方案是(原地址点我) Eclipse图标右键 ...

  3. XML和DTD的简单介绍和入门

    XML(Extensible Markup Lanaguage),可扩展标记语言,是标准通用标记语言(Standard Generalized Markup Language, SGML)的一个子集, ...

  4. [译]Stairway to Integration Services Level 11 - 日志配置

    介绍 在前一个章节我们讨论了事先行为,分享了如何操作默认的行为和时间冒泡,并且介绍了父子模型. 本文中,我们会配置SSIS日志. 进行简单及高级日志配置,存储,和检索的实验.并且生成自定义日志信息. ...

  5. C# Best Practices - Specify Clear Method Parameters

    Improve parameters parameter order public OperationResult PlaceOrder(Product product, int quantity, ...

  6. Node.js学习笔记1(简介)

            1.什么是Node.js?         Node.js,或者 Node,是一个可以让 JavaScript 运行在服务器端的平台.它可以让 JavaScript 脱离浏览器的束缚运 ...

  7. 高质量程序设计指南C/C++语言——C++/C常量

  8. drawInRect:withAttributes:

    - (void)drawRect:(CGRect)frame { NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defa ...

  9. 如何正确的在java web配置数据池

    在tomcat context.xml中配置数据 <Context reloadable="true"> <!-- Default set of monitore ...

  10. SkipFish

    https://zmap.io/ http://code.google.com/p/skipfish/wiki/SkipfishDoc#How_to_run_the_scanner?