Search code examples
javascriptdesign-patternsfunctional-programmingprototypal-inheritance

Variable privacy in Javascript's behaviour delegation pattern


Since my last question, I've been studying Javascript's prototype model and trying to get rid of the OOP vision I inherited from other languages (pun slightly intended).

I went back to basics and read Crookford's Javascript: The Good Parts, along with You Don't Know JS material and decided to stick with the so called behaviour delegation.

Restructuring my previous example implementing behaviour delegation and namespacing, I wrote:

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);

So, everytime I copy a new Archer object:

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

only this object will be created, conserving memory.

But what if I don't want to expose "accuracy" attribute? The attribute would only increase by calling a "training()" method or something similar. I tried to use var accuracy inside the anonymous function, but it turns into kind of static variable, all instances of Archer would share the same value.

The question: Is there any way to set a variable as private while still keeping behaviour-delegation/prototypal pattern?

I do know of functional pattern as well, Here I succesfully achieved variable privacy, at the cost of memory. By going functional, every new "archer" instance generates a new "Warrior" and then a new "Archer". Even considering that Chrome and Firefox have different optmizations, testings on both report that the Delegation/Prototypal pattern is more efficient:

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

If I go with the pure-object delegation pattern, should I just forget the classic encapsulation concept and accept the free changing nature of properties?


Solution

  • I would try to answer your question with something that is slightly different then this one that tells you how to use a library. Different in that it will(hopefully) give you some ideas of how can we tackle the problem of private vars in OLOO ourselves. At least to some extent, with our own code, no external lib needed, which could be useful in certain scenarios.


    In order for code to be cleaner I've striped your anonymous wrapper functions, since they are not related to problem in any way.

    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
    

    Test code above here. (and open your browser console)

    Usage

    1 ) General practice in OLOO about naming functions on different levels of prototype chain is apposite from OOP. We want different names that are more descriptive and self documenting, which brigs code that is cleaner and more readable. More importantly, by giving different names we avoid recursion loop:

    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 ) We've created an attachPocket() function that creates internal variable pocket. Creates new object with Object.create() and sets it's prototype to point to Archer. Pause. If you notice, functions that required a private var we have defined so that each of them take one more parameter(pocket), some use just pocket. Here is the trick.

    By making wrapper functions like archer.setAccuracy(), archer.getAccuracy() ... we can create closures and call directly functions that need private var (here pocket) and pass it to them as an argument.

    Like so:

     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); 
       }; 
       ...
     }
    

    Essencially by doing this we are bypassing what would have been a normal search for functions in prototype chain, just for functions that have need for a private var. This is what "call directly" refers to. By setting the same name for a function in archer("instance") like it is in prototype chain (Archer) we are shadowing that function at the instance level. No danger of having indefinite loops, since we are "calling directly" like stated above. Also by having the same function name we preserve the normal, expected behaviour of having access to same function in an "instance" like it is in a prototype chain. Meaning that afther var archer = Object.create(Archer) we have access to function setAccuracy like we would if it had been normal search for function in prototype chain.

    3 ) Every time attachPocket() is invoked it creates a new "instance" that has those wrapper functions that pass a pocket argument (all as an internal detail of implementation). And therefore every instance has own, unique, private variable.

    You would use functions in an "instance" normally:

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

    Scalability

    Up to now all we have is function that "attaches a pocket" with hardcoded values like Archer.setAccuracy, Archer.getAccuracy. What if we would like to expand prototype chain by introducing a new object type like this var AdvancedArcher = Object.create(Archer), how the attachPocket is going to behave if we pass to it AdvancedArcher object that might not even have setAccuracy() function? Are we going to change attachPocket() each time we introduce some change in prototype chain ?

    Let's try to answer those questions, by making attachPocket() more general.


    First, expand prototype chain.

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

    More generic attachPocket.

    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());
    

    Test the code here.

    In this more generic attachPocketGen as first argument we have a warriorType variable that represents any object in our prototype chain. Arguments that may fallow are ones that represent names of functions that need a private var.

    attachPocketGen takes those function names and makes wrapper functions with same names in archer "instance". Shadowing, just like before. Another thing to recognise is that this model of making wrapper functions and using apply() function to pass variables from closures is going to work for functions that use just pocket, functions that use pocket and other variables, and when ,of course, those variables use the relative this reference in front of them. So we have achieved somewhat more usable attachPocket, but that are still things that should be noticed.

    1) By having to pass names of functions that need private var, that usage implies that we(attachPocketGen users) need to know whole prototype chain (so we could see what functions need private var). Therefore if you are to make a prototype chain like the one here and just pass the attachPocketGen as an API to the programmer that wants to use your behaviour-delegation-with-private-variables, he/she would had to analyse objects in prototype chain. Sometimes that is not what wee want.

    1a) But we could instead, when defining our functions in prototype chain (like Archer.getAccuracy) to add one property to them like a flag that can tell if that function have need for a private var:

    Archer.getAccuracy.flg = true;

    And then we could add additional logic that checks all functions in prototype chain that have this flg and fills the funcsForPocket. Result would be to have just this call:

    var archer1 = attachPocketGen(AdvancedArcher)

    No other arguments except warriorType. No need for user of this function to have to know how prototype chain looks like, that is what functions have need for a private var.

    Improved style

    If we are to look at this code:

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

    We see that it uses "pocket" function setAccuracy. But we are not adding pocket here as last argument to it because setAccuracy that gets called is shadowed version, the one from instance. Becouse it will be called only from an instance like so archer1.archInit(...) That one is already adding a pocket as last argument so there is on need to. That's kinda nice, but the definition of it is:

    Archer.setAccuracy = function (value, pocket){ ...
    

    and that can be confusing when making objects in prototype chain like Archer.archInit above. If we look the definition of setAccuracy it looks like we should it. So in order not have to remember that we don't have to add pocket as last arg in functions (like archInit) that use other pocket functions, maybe we should to something like this:

    Archer.setAccuracy = function (value){  
       var pocket = arguments[arguments.length -1];
    
       pocket.accuracy = value;
    }
    

    Test the code here.

    No pocket as last argument in function definition. Now it's clear that function doesn't have to be called with pocket as an argument wherever in code.

    1 ) There are other arguably minor stuff to refer to when we think about more general prototype chain and attachPocketGen.Like making functions that use pocket callable when we dont wan't to pass them a pocket, that is to toggle pocket usage to a function, but in order not to make this post too long, let's pause it.

    Hope this can give you view ideas for solution to your question.