Javascript Patterns--读书笔记10 (Decorator)
Decorator模式是指可以在运行的时候让一些功能附加到对象上,当在静态类语言中实现的时候,也许不那么容易,但是在JS中,我们借助于它自己的语言特性,可以很容易的让其实现这种模式
应用场景
让我们来看一个这个模式应用的例子,假若你现在正在做一个卖东西的web应用。假设每一件新的商品,我们都抽像为一sale object,我们可以通过sale.getPrice()来得到商品的价格。假定这样一个场景,一个顾客在加拿大的Qubec省买了这样一件产品,那么他需要交纳联邦政府的税,还需要交纳Qubec省的税。我们可以这样来应用decorate模式,用联邦政府税decorator和Qubec省税deocraor来decorate它。我们来看下面的示例代码.
var sale = new Sale(100);//商品的价格是100 sale = sale.decorate('fedtax');//联邦征税 sale = sale.decorate('quebec');//省征税 sale = sale.decorate('money');//加上税以后的价格 sale.getPrice();//112.88
而在另一个场景中,那个购买者可能是另外一个省的,而这个省对这件商品是不征税的,而不过它在结算的时候,用的是加拿大元,而不再是美元,那让我们来看下面的代码:
var sale = new Sale(100); sale = sale.decorate('fedtax'); sale = sale.decorate('cdn');//改一下货币单位 sale.getPrice();//"CDN$ 105.00"
我们可以灵活的在运行的时候,来对这个对象在不同的场景下来附加上不同的功能,从而得到不同价格,那我们来看一下,如何实现这种模式.
实现
一种实现的方式是使每个decorator是一个object,每个decorator都含有一些可以被复写的方法。每一个decorator实际上继承了上一次被前一个decorator增加的方法。每一个要执行decorate的方法都会调用它父类的相同名称的方法,然后它父对象的方法又会执行和它一样的方法。
最后的效果是当你执行sale.getPrice()的时候,你会先调用money decorator,即sale.decorate('money')或者是sale.decorate('cdn'),然后money deocrator又会调用它父对象的相同名称的方法即quebec.getPrice()最后到Sale()构造函数实现的那个getPrice(),请看下图
我们首先实现一个构造函数,然后添加到prototype中一个方法:
funciton Sale(price) { this.price = price || 100; } Sale.prototype.getPrice = function() { return this.price; }
decocrators都被实现成构造函数的属性
Sale.decorators = {};
来看deocrator的具体实现,它们实现了自己的getPrice().请注意这些getPrice()首先会去调用parent object的getPrice()
Sale.decorators.fedtax = { getPrice : function() { var price = this.uber.getPrice(); price += price * 5 /100; return price; } } 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); } }
最后让我们来看一下deocrate是如何实现的,首先让我们来看一下,如何调用decorate, sale = sale.decorate('fedtax');'fedtax'它对应着一个object, Sale.decorators.fedtax.新的deocrate以后的newobj将会继承现在已有的对象,要么是最初的对象,要么是已经被装饰过的。为了实现继承部分,我们用到一个暂时的空constructor,我们还设置uber,让它的children可以访问parent.
Sale.prototype.decorate = function(decorator) { var F = function () {}; overrides = this.constructor.decorators[decorator],//请注意这里的构造函数没有被改过,始终是Sale() i, newobj; F.protoype = this;//要么是原来的new Sale(),要么是被装饰过的object newobj = new F(); newobj.uber = F.prototype; for( i in overrides) { if( overrides.hasOwnProperty(i)) { newobj[i] = overrides[i]; } } return newobj; }
用List()来实现
我们把所有的deocrator对象全部加到一个list中,然后当我们需要计算价格的时候,我们只需要遍历整个list,让所有的decorator对象执行所有的decorate方法即可:
function Sale(price) { this.price = (price >0) || 100; this.decorators_list = []; } Sale.decorators = {}; Sale.decorators.fedtax = { getPrice: function(price) { return price + price * 5 / 100; } }; Sale.decorators.quebec = { getPrice: function(price) { return price + price * 7.5 / 100; } }; Sale.decorators.money = { getPrice: function(price) { return "$" + price.toFixed(2); } }; Sale.prototype.deocrate = function (decorator) { this.decorators_list.push(decorator); }; Sale.prototype.getPrice = function () { var price = this.price, i, max = this.decorators_list.length, name; for(i =0; i < max; i+=1) { name = this.decorators_list[i]; price = Sale.decorators[name].getPrice(price); } return price; };