Javascript行为委托模式

时间:2015-09-23 19:36:26

标签: javascript design-patterns functional-programming prototypal-inheritance

从我的last question开始,我一直在研究Javascript的原型模型,试图摆脱我从其他语言继承的(有点意图)。

我回到基础并阅读了Crookford的Javascript: The Good Parts以及You Don't Know JS材料,并决定坚持所谓的行为授权

重构我之前的实现行为委托和命名空间的示例,我写道:

var GAME = {};

(function(namespace) {
    var Warrior = {};

    Warrior.init = function(weapon) {
        this.setWeapon(weapon);
    };

    Warrior.getWeapon = function() {
      return this.weapon;
    };

    Warrior.setWeapon = function(value) {
      this.weapon = value || "Bare hands";
    };

    namespace.Warrior = namespace.Warrior || Warrior;
})(GAME);

(function(namespace) {
    var Archer = Object.create(namespace.Warrior);

    Archer.init = function(accuracy) {
        this.setWeapon("Bow");
        this.setAccuracy(accuracy);
    };

    Archer.getAccuracy = function() {
        return this.accuracy;
    };

    Archer.setAccuracy = function(value) {
      this.accuracy = value;
    };

    namespace.Archer = namespace.Archer || Archer;
})(GAME);

所以,每次我复制一个新的Archer对象:

var archer1 = Object.create(GAME.Archer);

只会创建此对象,从而节省内存。

但是,如果我不想公开“准确性”属性怎么办?该属性只会通过调用“training()”方法或类似方法来增加。我试图在匿名函数中使用var accuracy,但它变成了一种静态变量,Archer的所有实例都会共享相同的值。

问题:有没有办法将变量设置为私有,同时仍然保持行为委派/原型模式?

我也知道功能模式,在这里,我成功地以内存为代价实现了可变隐私。通过功能,每个新的“射手”实例生成一个新的“战士”,然后一个新的“射手”。即使考虑到Chrome和Firefox有不同的优化,两者的测试都报告了委托/原型模式更有效:

http://jsperf.com/delegation-vs-functional-pattern

如果我选择纯对象委托模式,我是否应该忘记经典的封装概念并接受属性的自由变化性质?

1 个答案:

答案 0 :(得分:1)

我会尝试使用与此one略有不同的内容来回答您的问题,该内容会告诉您如何使用库。 不同之处在于它(希望)会给你一些关于我们如何在OLOO中解决私人变量问题的想法。至少在某种程度上,使用我们自己的代码,不需要外部库,这在某些情况下可能很有用。

为了使代码更清晰,我已经对您的匿名包装函数进行了条带化​​处理,因为它们与问题没有任何关系。

var Warrior = {};

Warrior.warInit = function (weapon){
   this.setWeapon(weapon);    
}

Warrior.getWeapon = function(){
   return this.weapon;
}

Warrior.setWeapon = function (value){
   this.weapon = value || "Bare hands";
}

var Archer = Object.create(Warrior);

Archer.archInit = function (accuracy){
   this.setWeapon("Bow");   
   this.setAccuracy(accuracy); 
}

Archer.getAccuracy = function (pocket) {
   return pocket.accuracy;
}
Archer.setAccuracy = function (value, pocket){
   pocket.accuracy = value;
}

function attachPocket(){ 

   var pocket = {};

   var archer = Object.create(Archer); 

   archer.getAccuracy = function(){
      var args = Array.prototype.slice.call(arguments);
      args = args.concat([pocket]); 

      return Archer.getAccuracy.apply(this, args)
   }; 
   archer.setAccuracy = function(){ 
      var args = Array.prototype.slice.call(arguments);
      args = args.concat([pocket]); 

      return Archer.setAccuracy.apply(this, args);
   }; 

   return archer;
}


var archer1 = attachPocket();  

archer1.archInit("accuracy high"); 
console.log(archer1.getAccuracy()); // accuracy high
archer1.setAccuracy("accuracy medium");
console.log(archer1.getAccuracy()); // accuracy medium

测试here以上的代码。 (并打开浏览器控制台)

用法

1) OLOO关于在不同级别的原型链上命名函数的一般做法是OOP的相似之处。我们希望不同的名称更具描述性和自我记录,这些代码更清晰,更易读。更重要的是,通过给出不同的名称,我们避免了递归循环:

Archer.init = function(accuracy, pocket){
     this.init() // here we reference Archer.init() again, indefinite recurson. Not what we want 
      ...
}
Archer.archInit = fucntion (accuracy, pocket){ // better,
     this.warInit() // no name "collisions" .
}

2)我们创建了一个创建内部变量口袋的attachPocket()函数。使用Object.create()创建新对象并将其原型设置为指向Archer。暂停。如果你注意到,我们已经定义了需要私有var的函数,以便每个函数都需要一个参数(口袋),有些只使用口袋。这是诀窍。

  

通过制作像archer.setAccuracy()这样的包装函数,archer.getAccuracy()   ...我们可以创建闭包并直接调用需要的函数   private var(这里是pocket)并将它作为参数传递给它们。

像这样:

 function AtachPocket(){
   ...
   var pocket = {};

   archer.setAccuracy = function(){ 
     var args = Array.prototype.slice.call(arguments);
     args = args.concat([pocket]); // appending pocket to args
     return Archer.setAccuracy(this, args); 
   }; 
   ...
 }

通过这样做,我们绕过了原型链中的函数的正常搜索,仅适用于需要私有var的函数。这就是"直接打电话"是指。 通过为archer(" instance")中的函数设置相同的名称,就像它在原型链(Archer)中一样,我们在实例级别隐藏该函数。没有无限循环的危险,因为我们直接打电话"如上所述。同样,通过使用相同的函数名,我们保留了在"实例中访问相同函数的正常预期行为。就像在原型链中一样。这意味着var archer = Object.create(Archer)我们可以访问函数setAccuracy,就像我们在原型链中正常搜索函数一样。

3)每次调用attachPocket()时,它都会创建一个新的"实例"具有传递口袋参数的那些包装函数(所有这些都作为实现的内部细节)。因此,每个实例都有自己独特的私有变量。

你可以在"实例中使用函数"通常:

archer1.archInit("accuracy high"); // Passing needed arguments.
                               // Placed into pocked internally.
archer1.getAccuracy(); // Getting accuracy from pocket.

可扩展性

到目前为止,我们所有的功能都是"附上一个口袋"使用Archer.setAccuracyArcher.getAccuracy等硬编码值。如果我们想通过引入像var AdvancedArcher = Object.create(Archer)这样的新对象类型来扩展原型链,如果我们传递给它attachPocket的对象可能甚至没有AdvancedArcher功能?每当我们在原型链中引入一些变化时,我们是否会改变setAccuracy()

让我们通过使attachPocket()更加通用来尝试回答这些问题。

首先,扩展原型链。

attachPocket()

更通用的var AdvancedArcher = Object.create(Archer); AdvancedArcher.advInit = function(range, accuracy){ this.archInit(accuracy); this.setShotRange(range); } AdvancedArcher.setShotRange = function(val){ this.shotRange = val; }

attachPocket

测试代码here

在这个更通用的function attachPocketGen(warriorType){ var funcsForPocket = Array.prototype.slice.call(arguments,1); // Take functions that need pocket var len = funcsForPocket.length; var pocket = {}; var archer = Object.create(warriorType); // Linking prototype chain for (var i = 0; i < len; i++){ // You could use ES6 "let" here instead of IIFE below, for same effect (function(){ var func = funcsForPocket[i]; archer[func] = function(){ var args = Array.prototype.slice.call(arguments); args = args.concat([pocket]); // appending pocket to args return warriorType[func].apply(this, args); } })() } return archer; } var archer1 = attachPocketGen(Archer,"getAccuracy","setAccuracy"); archer1.advInit("11","accuracy high"); console.log(archer1.getAccuracy()); // "accuracy high"; archer1.setAccuracy("accuracy medium"); console.log(archer1.getAccuracy()); 作为第一个参数中,我们有一个attachPocketGen变量,它代表我们原型链中的任何对象。可以休闲的参数是表示需要私有var的函数名称的参数。

warriorType获取这些函数名称,并在attachPocketGen&#34; instance&#34;中生成具有相同名称的包装函数。阴影,就像以前一样。 另一件需要认识的是,这个制作包装函数的模型和使用archer函数从闭包中传递变量的模型适用于只使用口袋的函数,使用口袋和其他变量的函数,当然,这些变量在它们前面使用相对apply()引用。 所以我们已经实现了一些更有用的attachPocket,但这仍然是应该注意的事情。

1)通过必须传递需要私有var的函数的名称,该用法意味着我们(this用户)需要知道整个原型链(所以我们可以看到什么函数需要私有var)。因此,如果您要创建一个类似于此处的原型链,并将attachPocketGen作为API传递给想要使用您的行为委托与私有变量的程序员,他/她将不得不分析原型链中的对象。有时这不是我们想要的。

1a)但是我们可以在原型链中定义我们的函数(比如attachPocketGen)来向它们添加一个属性,就像一个标志,可以判断该函数是否需要私人var:

Archer.getAccuracy

然后我们可以添加额外的逻辑来检查具有此Archer.getAccuracy.flg = true;的原型链中的所有函数并填充flg。 结果是只有这个电话:

funcsForPocket

除了var archer1 = attachPocketGen(AdvancedArcher)之外没有其他参数。这个函数的用户不需要知道原型链是什么样的,这就是私有var所需的函数。

改进风格

如果我们要查看此代码:

warriorType

我们看到它使用&#34; pocket&#34;函数Archer.archInit = function (accuracy){ this.setWeapon("Bow"); this.setAccuracy(accuracy); } 。但是我们不是在这里添加口袋作为它的最后一个参数,因为被调用的setAccuracy是阴影版本,来自实例的版本。因为它只会从像setAccuracy之类的实例中调用 那个人已经添加了一个口袋作为最后一个参数,所以有需要。这很好,但它的定义是:

archer1.archInit(...)

在上面的Archer.setAccuracy = function (value, pocket){ ... 之类的原型链中制作对象时可能会造成混淆。如果我们看一下Archer.archInit的定义,我们就应该这样做了。因此,为了不必记住我们不必添加口袋作为使用其他口袋功能的函数(如setAccuracy)中的最后一个arg,也许我们应该像这样:

archInit

测试代码here

没有口袋作为函数定义中的最后一个参数。现在很清楚,无论在代码中,函数都不必用pocket作为参数调用。

1)当我们考虑更一般的原型链和Archer.setAccuracy = function (value){ var pocket = arguments[arguments.length -1]; pocket.accuracy = value; } 时,还有其他可以说是次要的东西。就像制作使用口袋可调用的功能时,我们不会这样做。为了传递一个口袋,即将口袋用法切换到一个功能,但是为了不让这个帖子太长,让我们暂停它。

希望这可以让您查看解决问题的想法。