【问题标题】:Javascript Decorator Pattern by Addy OsmaniAddy Osmani 的 Javascript 装饰器模式
【发布时间】:2015-08-06 04:26:32
【问题描述】:

鉴于此代码:

// Constructor.
var Interface = function (name, methods) {
        if (arguments.length != 2) {
            throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2.");
        }
        this.name = name;
        this.methods = [];
        for (var i = 0, len = methods.length; i < len; i++) {
            if (typeof methods[i] !== 'string') {
                throw new Error("Interface constructor expects method names to be " + "passed in as a string.");
            }
            this.methods.push(methods[i]);
        }
    };


// Static class method.
Interface.ensureImplements = function (object) {
    if (arguments.length < 2) {
        throw new Error("Function Interface.ensureImplements called with " + arguments.length + "arguments, but expected at least 2.");
    }
    for (var i = 1, len = arguments.length; i < len; i++) {
        var interface = arguments[i];
        if (interface.constructor !== Interface) {
            throw new Error("Function Interface.ensureImplements expects arguments" + "two and above to be instances of Interface.");
        }
        for (var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
            var method = interface.methods[j];
            if (!object[method] || typeof object[method] !== 'function') {
                throw new Error("Function Interface.ensureImplements: object " + "does not implement the " + interface.name + " interface. Method " + method + " was not found.");
            }
        }
    }
};


function extend( a, b ){
    for( var key in b )
        if( b.hasOwnProperty(key) )
            a[key] = b[key];
    return a;
}




var Macbook = new Interface( "Macbook",
  ["addEngraving",
  "addParallels",
  "add4GBRam",
  "add8GBRam",
  "addCase"]); /* Returns an object that stores the name of the Interface and the array of methods (pushed) */

// A Macbook Pro might thus be represented as follows:
var MacbookPro = function(){
    // implements Macbook
};

MacbookPro.prototype = {
    addEngraving: function(){
    },
    addParallels: function(){
    },
    add4GBRam: function(){
    },
    add8GBRam:function(){
    },
    addCase: function(){
    },
    getPrice: function(){
      // Base price
      return 900.00;
    }
};


var MacbookDecorator = function( macbook ){

    Interface.ensureImplements( macbook, Macbook );
    this.macbook = macbook;

};

MacbookDecorator.prototype = {
    addEngraving: function(){
        return this.macbook.addEngraving();
    },
    addParallels: function(){
        return this.macbook.addParallels();
    },
    add4GBRam: function(){
        return this.macbook.add4GBRam();
    },
    add8GBRam:function(){
        return this.macbook.add8GBRam();
    },
    addCase: function(){
        return this.macbook.addCase();
    },
    getPrice: function(){
        return this.macbook.getPrice();
    }
};


var CaseDecorator = function( macbook ){
   this.macbook = macbook;
};

// Let's now extend (decorate) the CaseDecorator
// with a MacbookDecorator
extend( CaseDecorator, MacbookDecorator );

CaseDecorator.prototype.addCase = function(){
    return this.macbook.addCase() + "Adding case to macbook";
};

CaseDecorator.prototype.getPrice = function(){
    return this.macbook.getPrice() + 45.00;
};


var myMacbookPro = new MacbookPro();

// Outputs: 900.00
console.log( myMacbookPro.getPrice() );


// Decorate the macbook
var decoratedMacbookPro = new CaseDecorator( myMacbookPro );

// This will return 945.00
console.log( decoratedMacbookPro.getPrice() );

第一眼看起来不错。 但是最近我在更深入地分析这段代码时遇到了一些我今天想与大家分享的问题。 开始吧: 这段代码的当前版本包含这一段:

MacbookDecorator.prototype = {
    addEngraving: function(){
        return this.macbook.addEngraving();
    },
    addParallels: function(){
        return this.macbook.addParallels();
    },
    add4GBRam: function(){
        return this.macbook.add4GBRam();
    },
    add8GBRam:function(){
        return this.macbook.add8GBRam();
    },
    addCase: function(){
        return this.macbook.addCase();
    },
    getPrice: function(){
        return this.macbook.getPrice();
    }
};

在这一点上实际上没有意义(如果我错了,请抓住我),尤其是在使用给定方式调用蜂时:

var myMacbookPro = new MacbookPro();

// Outputs: 900.00
console.log( myMacbookPro.getPrice() );


// Decorate the macbook
var decoratedMacbookPro = new CaseDecorator( myMacbookPro );

// This will return 945.00
console.log( decoratedMacbookPro.getPrice() );

MacbookDecorator.prototype 在给定的上下文中毫无意义。 你们中的一些人可能会说它是链条的一部分,因此 MacbookDecorator 的原型在那里占有一席之地。 但是没有。 它在任何地方都没有被称为实际用途。 为什么?

extend( CaseDecorator, MacbookDecorator );

也许你们中的一些人相信,这就是魔法发生的部分,但请记住这一点:

function extend( a, b ){
    for( var key in b )
        if( b.hasOwnProperty(key) )
            a[key] = b[key];
    return a;
}

CaseDecorator 并没有从 MacbookDecorator 继承,事实上,这种方式的原型被完全忽略并且没有被 CaseDecorator 继承。 此外,extend 函数根本没有用,因为它只会从对象中读取属性,但是提供的两个参数都是函数,因此,如果函数至少在传递之前没有执行,则不会发生继承,这很棘手,因为此时甚至没有定义 this.macbook。

这是我的代码版本,实际上做了完全相同的事情,省略了不必要的部分和部分非常烦人的部分:

// Constructor.
var Interface = function (name, methods) {
        if (arguments.length != 2) {
            throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2.");
        }
        this.name = name;
        this.methods = [];
        for (var i = 0, len = methods.length; i < len; i++) {
            if (typeof methods[i] !== 'string') {
                throw new Error("Interface constructor expects method names to be " + "passed in as a string.");
            }
            this.methods.push(methods[i]);
        }
    };


// Static class method.
Interface.ensureImplements = function (object) {
    if (arguments.length < 2) {
        throw new Error("Function Interface.ensureImplements called with " + arguments.length + "arguments, but expected at least 2.");
    }
    for (var i = 1, len = arguments.length; i < len; i++) {
        var interface = arguments[i];
        if (interface.constructor !== Interface) {
            throw new Error("Function Interface.ensureImplements expects arguments" + "two and above to be instances of Interface.");
        }
        for (var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
            var method = interface.methods[j];
            if (!object[method] || typeof object[method] !== 'function') {
                throw new Error("Function Interface.ensureImplements: object " + "does not implement the " + interface.name + " interface. Method " + method + " was not found.");
            }
        }
    }
};




var Macbook = new Interface( "Macbook",
  ["addEngraving",
  "addParallels",
  "add4GBRam",
  "add8GBRam",
  "addCase"]); /* Gives back an object that stores the name of the Interface and the array of methods (pushed) */

// A Macbook Pro might thus be represented as follows:
var MacbookPro = function(){};

var decorator = function() {
  return {
    addEngraving: function(){
    },
    addParallels: function(){
    },
    add4GBRam: function(){
    },
    add8GBRam:function(){
    },
    addCase: function(){
    },
    getPrice: function(){
      // Base price
      return 900.00;
    }
  }
} 
MacbookPro.prototype = decorator();
//var MacbookDecorator = function( macbook ){
//    Interface.ensureImplements( macbook, Macbook );
//    this.macbook = macbook;
//}
/*MacbookDecorator.prototype = decorator();*/


var CaseDecorator = function( macbook ){
   this.macbook = macbook;
};


CaseDecorator.prototype.addCase = function(){
    return this.macbook.addCase() + "Adding case to macbook";
};

CaseDecorator.prototype.getPrice = function(){
    return this.macbook.getPrice() + 45.00;
};


// Decorate the macbook
var decoratedMacbookPro = new CaseDecorator( new MacbookPro() );

// This will return 945.00
console.log( decoratedMacbookPro.getPrice() );

您对此有何看法,作者是否遗漏了什么,或者是真的吗?

【问题讨论】:

  • 您说您遗漏了代码中不必要的部分。但是那你为什么不省略MacbookDecorator(它从来没有在任何地方使用过),你为什么要写一个额外的decorator() 函数(它只被调用一次)?
  • 哦,那是正确的,不应该在那里。好吧,decorator() 函数可以提供一个函数来返回“骨架”,但是对于这种类型的“装饰”(仅调用一次),我完全同意

标签: javascript prototype decorator


【解决方案1】:
extend( CaseDecorator, MacbookDecorator );

没有从 MacbookDecorator 继承 CaseDecorator,事实上,这种方式的原型被完全忽略并且没有被 CaseDecorator 继承。

你完全正确。对我来说,这似乎是作者的失误,实际上应该是

extend(CaseDecorator.prototype, MacbookDecorator.prototype);

甚至是完整的原型继承设置。

extend 只会从对象中读取属性,但是提供的两个参数都是函数

不要忘记函数也是对象,所以如果它们只有自己的属性,extend 可以对它们做很多事情。但你是对的,在这种情况下它什么也不做,因为像 .prototype.name 这样的典型函数实例属性是不可枚举的。

【讨论】:

  • 哦,是的,'Function 构造函数创建了一个新的 Function 对象。在 JavaScript 中,每个函数实际上都是一个 Function 对象。 .谢谢你的回答!
猜你喜欢
  • 1970-01-01
  • 2018-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-07
  • 1970-01-01
  • 2018-04-10
相关资源
最近更新 更多