Javascript装饰模式 - 原型还是单一功能?

时间:2014-09-29 19:09:06

标签: javascript design-patterns decorator

我正在浏览Addy Osmani关于装饰器模式的教程(在这里找到http://addyosmani.com/blog/decorator-pattern/),我对如何在Javascript中实现最简单的Decorator感到困惑。似乎有些示例使用obj.prototype模式向现有对象添加功能,有些示例创建独立函数并传递对象。

// Decorator Pattern ?
function coffee(size, flavors) {
    this._size = size || "medium";
    this._flavors = flavors || [];   
    this._cost = 100;
    this.info = function() {
        console.log(this._size, this._flavors, this._cost);
    }
}

// Decorator - Would this be considered a decorator since the functionality needed to add flavors default to the coffee object?

function addFlavor(coffee, flavor) {
    coffee._flavors.push(flavor);
    coffee._cost = coffee._cost + 25;
}

// Decorator - Engrave the cup? lol
function engraving(coffee) {
    coffee._cost = coffee._cost + 200;
}

// Decorator Variation w/ prototype instead - Add cream
coffee.prototype.addCream = function () { 
    this._cost = this._cost + 100;
};

// Instantiate Coffee
testCoffee = new coffee('Large', ['vanilla']);

// Add Flavors
addFlavor(testCoffee, 'chocolate');
addFlavor(testCoffee, 'almond');
addFlavor(testCoffee, 'hazelnut');

// Add Engraving
engraving(testCoffee);

// Add Cream
testCoffee.addCream();

// Log it all to the console
testCoffee.info();

可以在此处找到此示例的JsFiddle:http://jsfiddle.net/pathsofdesign/ocbbzoy2/


我的问题:看起来我可以使用原型继承来实现Decorator模式。这样做是否有任何利弊(即:我的addCream()方法)? 谢谢!

更新:

看起来我的示例实际上根本没有实现Decorator模式。 @Etai和@Bergi都在下面给出了很好的答案。如果我理解正确,传统的JS装饰器'包装'另一个obj然后调整该特定对象的行为而不修改基础对象。

2 个答案:

答案 0 :(得分:4)

没有。你所拥有的不是装饰者,他们只是方法。方法通过覆盖方法来改变对象,装饰器变异行为。它们与 mixins 非常相似,只是它们没有创建新方法。

例如,让我们为Coffee课程提供setSize方法:

Coffee.prototype.setSize = function(size) {
    this._size = size || 'medium';
};

现在,让我们有一个疯狂的咖啡师谁没有正确的比例:

function extreme(coffee) {
    var oldMethod = coffee.setSize;
    coffee.setSize = function(size) {
        oldMethod.call(this, size && 'Xtra'+size[0].toUpperCase()+size.slice(1));
    };
}

让他服务一个被命名为“大”的人:

> var coffee = extreme(new Coffee);
> coffee.setSize("large")
> coffee.info()
XtraLarge, Array [], 100

答案 1 :(得分:2)

我认为该教程非常混乱。

假设您要创建一个simpleItem作为基本项目,并创建一个complexItem,它是您的simpleItem加上更多。

使用原型继承:

function SimpleItem(name){
    this.name = name;
}

function ComplexItem(size){
   this.size = size;
}
ComplexItem.prototype = new SimpleItem('complex');
var item = new ComplexItem(3); //{size: 3, name: 'complex'}
ComplexItem.prototype.name = 'new complex name'; //item is now {size: 3, name: 'new complex name'}

使用装饰器模式:

function SimpleItem(name){
    this.name = name;
}

function ComplexItem(size){
    SimpleItem.call(this, 'complex');
    this.size = size;
}

var item = new ComplexItem(3); //{size: 3, name: 'complex'}
ComplexItem.prototype.name = 'new complex name'; //item is still {size: 3, name: 'complex'}

虽然看起来ComplexItem继承自SimpleItem,但事实并非如此。它由它装饰。 这与此相同:

function decorateMe(name){
    this.name = name;
}

function ComplexItem(size){
    decorateMe.call(this, 'complex');
    this.size = size;
}

这允许类似于“多重继承”的东西,但稍后在父级中更改某些内容不会影响已创建的后代。

通常,装饰器模式意味着您通过执行一些将改变它的代码来“装饰”您的实例。

编辑:请注意,虽然我通过装饰属性使用了这个例子,正如@Bergi所说,装饰器实际上用于装饰行为(功能)。 @ Bergi的答案实际上是经典的装饰器模式,它用新的方法包装旧方法,从而“装饰”它。 我的例子更多的是mixin / extend模式。但是,该模式的主要思想是您在运行时更改它,而不是实际继承自另一个原型。