JavaScript学习笔记(四十四) 装饰器

装饰器模式(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;
};

使用List的实现(Implementation Using a List)

让我们来探讨一个稍微不同的实现,这个实现得益于JavaScript的动态特性且压根不需要使用继承。取代前面的每个装饰方法调用在链中的前一个方法,我们可以简单的传递前一个方法的结果给下一个方法。

这种实现也可以很容易的不装饰(undecorating)或取消一个装饰(undoing a decoration),意味着简单从装饰器list中移除一项。

用例将会稍微简单一点因为我们不需要将decorate()的返回值赋值给对象。在这个实现中,decorate()不会对对象做任何事情,它简单的给一个list附加装饰器:
var sale = new Sale(100); // the price is 100 dollars
sale.decorate('fedtax'); // add federal tax
sale.decorate('quebec'); // add provincial tax
sale.decorate('money'); // format like money
sale.getPrice(); // "$112.88"
这个Sale()构造方法现在有一个装饰器的list作为自身属性:
function Sale(price) {
    this.price = (price > 0) || 100;
    this.decorators_list = [];
}
可访问的装饰器再次被作为Sale.decorators的属性实现。注意现在的getPrice()方法更简单了因为它们不需要调用parent的getPrice()方法去获得中间值( intermediate result);这个值被作为参数传递给getPrice()方法。
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);
    }
};
有趣的部分发生在parent的decorate()和getPrice()方法。在上一个实现中,decorate()是有点复杂且getPrice()十分简单。在这个实现中是恰恰相反: decorate()仅仅给list附加装饰器而getPrice()做所有的工作。这个工作包括遍历当前添加装饰器的list并调用每个装饰器的getPrice()方法,传递从前一个方法获得结果。
Sale.prototype.decorate = 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;
};
第二种装饰器模式的实现更简单,且不包含继承。包装方法也更简单。所有工作完成因为方法"同意(agree)"被装饰。在这个简单的实现中,getPrice()是唯一允许包装的方法。如果你希望有更多方法可以被装饰,那么遍历装饰器list的部分应该每个方法重复一次。然而,这能简单的抽象到一个辅助方法(helper method),接收一个方法并让它变成“可装饰的(decoratable)”。在那样的实现中,decorators_list属性将成为一个以方法名作为属性,值为装饰器对象数组的对象。







猜你喜欢

转载自blog.csdn.net/qq838419230/article/details/10363229