自从last question以来,我一直在研究Javascript的原型(prototype)模型,并试图摆脱我从其他语言继承的OOP愿景(双关语意)。

我回到基础知识,阅读了Crookford的Javascript: The Good PartsYou Don't Know JS Material ,并决定坚持所谓的行为委托(delegate)

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

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实例将共享相同的值。

问题:有什么方法可以将变量设置为私有(private)变量,同时仍保持行为委托(delegate)/原型(prototype)模式?

我确实也知道功能模式,在这里我以内存为代价成功实现了可变隐私。通过运行,每个新的“archer”实例都会生成一个新的“Warrior”,然后生成一个新的“Archer”。即使考虑到Chrome和Firefox具有不同的优化,对两者的测试也表明委派/原型(prototype)模式更有效:

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

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

最佳答案

我会尝试用与one稍有不同的方式回答您的问题,该here告诉您如何使用库。
不同之处在于它(希望)为您提供一些想法,使我们可以如何解决OLOO中的私有(private)变量问题。至少在某种程度上,使用我们自己的代码,不需要外部lib,这在某些情况下可能很有用。

为了使代码更整洁,我对您的匿名包装器功能进行了划分,因为它们与问题完全无关。

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)在OOO中,有关在不同级别的原型(prototype)链上命名函数的命名的通用实践是对的。我们想要不同的名称,这些名称更具描述性和自我记录性,从而使代码更简洁,更易读。更重要的是,通过提供不同的名称,我们避免了递归循环:
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()函数,该函数创建内部变量Pocket。使用Object.create()创建新对象,并将其原型(prototype)设置为指向Archer。暂停。如果您注意到,我们定义了需要私有(private)var的函数,以便每个函数都使用一个以上的参数(pocket),其中一些仅使用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);
   };
   ...
 }

从本质上讲,通过这样做,我们绕过了通常在原型(prototype)链中搜索功能的过程,而只是对需要私有(private)变量的功能进行了搜索。这就是“直接致电”所指的。
通过像在原型(prototype)链(archer)中一样为Archer(“instance”)中的函数设置相同的名称,我们在实例级别隐藏了该函数。没有不确定的循环的危险,因为如上所述我们正在“直接调用”。同样,通过具有相同的功能名称,我们保留了在“实例”中访问相同功能的正常预期行为,就像在原型(prototype)链中一样。这意味着在var archer = Object.create(Archer)之后,我们可以访问setAccuracy函数,就像在原型(prototype)链中正常搜索函数时一样。

3)每次调用attachPocket()时,都会创建一个新的“实例”,该实例具有那些传递了Pocket参数的包装函数(全部作为实现的内部细节)。因此,每个实例都有自己的唯一私有(private)变量。

您通常会在“实例”中使用函数:
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)这样的新对象类型来扩展原型(prototype)链,如果我们将传递给它甚至没有attachPocket函数的AdvancedArcher对象传递给setAccuracy(),那么attachPocket()的行为将如何?每当我们在原型(prototype)链中引入一些更改时,是否都要更改attachPocket()

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

首先,扩展原型(prototype)链。
var AdvancedArcher = Object.create(Archer);

AdvancedArcher.advInit = function(range, accuracy){
    this.archInit(accuracy);
    this.setShotRange(range);
}
AdvancedArcher.setShotRange = function(val){
    this.shotRange = val;
}

更通用的attachPocketGen
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());

测试代码here

在这个更通用的warriorType作为第一个参数的情况下,我们有一个attachPocketGen变量,它表示原型(prototype)链中的任何对象。可能无法使用的参数代表需要私有(private)变量的函数的名称。
archer采用这些函数名称,并在apply()“instance”中使包装函数具有相同的名称。像以前一样阴影。
还要认识到的另一点是,这种制作包装器函数并使用this函数从闭包中传递变量的模型将适用于仅使用Pocket的函数,使用Pocket和其他变量的函数,以及当这些变量当然使用前面的相对attachPocketGen引用。
因此,我们已经获得了更多可用的attachPocket,但这仍然是需要注意的事情。

1)通过必须传递需要私有(private)var的函数名称,这种用法意味着我们(attachPocketGen用户)需要了解整个原型(prototype)链(因此我们可以看到哪些函数需要私有(private)var)。因此,如果您要像此处那样制作一个原型(prototype)链,并且只是将Archer.getAccuracy作为API传递给希望使用带有私有(private)变量的行为委托(delegate)的程序员,则他/她将不得不分析原型(prototype)链中的对象。有时那不是我们想要的。

1a)但是,当在原型(prototype)链中定义函数时(例如Archer.getAccuracy.flg = true;),我们可以改为向其添加一个属性,例如可以判断该函数是否需要私有(private)var的标志:
flg
然后,我们可以添加其他逻辑,以检查原型(prototype)链中具有此funcsForPocket的所有函数并填充var archer1 = attachPocketGen(AdvancedArcher)
结果是只有这个调用:
warriorType
setAccuracy外,没有其他参数。不需要此功能的用户就不必知道原型(prototype)链的外观,这就是私有(private)var需要什么功能。

改良风格

如果我们要看这段代码:
Archer.archInit = function (accuracy){
   this.setWeapon("Bow");
   this.setAccuracy(accuracy);
}

我们看到它使用“口袋”功能setAccuracy。但是我们没有在此处添加口袋作为最后一个参数,因为被调用的archer1.archInit(...)是影子版本,即实例的版本。因为它将仅从像Archer.archInit这样的实例中调用,因为它已经在最后一个参数中添加了一个口袋,因此有必要。挺好的,但是它的定义是:
Archer.setAccuracy = function (value, pocket){ ...

在上面的setAccuracy这样的原型(prototype)链中制作对象时,可能会造成混淆。如果我们查看archInit的定义,它看起来就应该如此。因此,为了不必记住我们不必在使用其他Pocket函数的函数(例如attachPocketGen)中添加pocket作为最后一个arg,也许我们应该这样做:
Archer.setAccuracy = function (value){
   var pocket = arguments[arguments.length -1];

   pocket.accuracy = value;
}

测试代码ojit_a。

没有口袋作为函数定义中的最后一个参数。现在很明显,无论代码在哪里,都不必使用Pocket作为参数来调用函数。

1)当我们考虑更通用的原型(prototype)链和ojit_code时,还有其他可以说是次要的东西。就像使函数在不需要传递口袋时使用可调用的Pocket一样,即将Pocket的使用切换为一个函数,但是为了不使这篇文章过长,让我们暂停一下。

希望这可以为您提供解决问题的想法。

关于javascript - Javascript行为委托(delegate)模式中的可变隐私,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32748078/

10-11 19:27