My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Sunday, February 24, 2008

How to inject protected methods in JavaScript

As you know, JavaScript requires different strategies to emulate private methods.
I personally wrote some weird experiment to automatically emulate private scope behaviour.

However, my experiment was RegExp based ... a really bad way to emulate private scope behaviour for a lot of reasons.

At the same time, you can implement private scope methods only inside the constructor and this means that you cannot base your code with both prototype and private scope.

// non sense example
function MyConstructor(value){

function privateMethod(){
return this.value;
}

this.get = function(){
return privateMethod.call(this);
}

this.value = value;
};

MyConstructor.prototype.getAgain = function(){
return privateMethod.call(this);
// error, privateMethod has a different scope
};


Basically, the reason that make prototype better than constructor with privileged methods, as get one is, is that core doesn't need to parse and/or create functions for each instance.
Another reason to prefer them is that you can add, remove, or change, a single function affecting every instance.
A good behaviour specially with a browser based language, as JavaScript is, that allow developers to write dedicated browser methods once, instead of check and change behaviour each time and for each instance.


The bridge strategy


One way to solve scope problems betwen external prototype based methods and private constructor functions, is to use a bridge.

// basic bridge example
function MyConstructor(value){

function privateMethod(){
return this.value;
};

this.callPrivateMethod = function(name, arguments){
return eval(name).apply(this, arguments);
};

this.value = value;
};

MyConstructor.prototype.get = function(){
return this.callPrivateMethod("privateMethod", arguments);
};

var demo = new MyConstructor("test");
alert(demo.get()); // test

Basically, the bridge method is a privileged wrapper, but it is inside the correct scope and that's why it can call function privateMethod without errors.
Seems cool? But it isn't so cool .. and that's why:

  • the bridge method is public, everyone could override them in every other place and for every instance

  • each instance creates every private function plus a bridge method, there's no prototype behaviour

  • last but not least, we are using eval ... and this means, for 99% of times, that we are using a bad code design



Public methods overload strategy


A different way to solve this problem is to inject your private methods inside the instance each time you call a public one.
You can do it once, and forget private scope methods for ever, using them in a natural way inside every other one.
To do it, you need to change at least one time each public method, overloading them during constructor assignment.
This is a final result example:

MyClass = Create(

// constructor
function(value){
this.value = value;
},{
// one or more prototypes
sum:function(num){
return this._checkNumAndSum(num);
}
},{
// one or more private methods
_checkNumAndSum:function(num){
alert(this.sum === MyClass.prototype.sum); // true
return typeof num === "number" && isFinite(num) ? this.value += num : undefined;
}
}
);

var demo = new MyClass(3);
alert(demo._checkNumAndSum); // undefined
alert(demo.sum(2)); // alert true and after them returned value is 5
alert(demo._checkNumAndSum); // undefined

This list rappresents Create function goals:

  • prototype based, it overloads directly the prototype object and it does not create protected functions each time

  • natural code, you do not need to change your code as is for bridge, during protected method execution


This one rappresents Create function limits:

  • you cannot share a prototype object, even if this phrase doesn't make sense for most of you

  • public methods execution time will be a bit slower, where that "bit" is not a real problem but for performances maniacs it will

  • injected methods are protected and not private, these are visible during method execution itself


To better understand last point, look at this example:

function visibleMethod(instance){
alert(instance._checkNumAndSum);
};

// ... same code of precedent example with this change

// one or more prototypes
sum:function(num){
visibleMethod(this);
return this._checkNumAndSum(num);
}


Since I guess that above situation is not so common, I think that's a good thing to let you know that protected methods are visible during each protected or public method execution.
Pure OOP has a different meaning for protected methods, it is based on possibility to use those methods from a subclass.
In my case, I choosed protected word because private one, usually not accessible from subclasses, is a word that loose its concept in the instant you can access that method outside the object.
I know probably my choice wasn't so serious, but for a prototype based inheritance I think it makes sense :)

Finally, what you really need to test my examples, is this function:

function Create(constructor, prototype, protected){
// (C) Andrea Giammarchi - Mit Style License
switch(typeof protected){
case "object":
var list = [],
i = 0,
key;
for(key in protected)
if(protected.hasOwnProperty(key))
list[i++] = {key:key, callback:protected[key]};
for(var key in prototype)
if(prototype.hasOwnProperty(key))
prototype[key] = (function(prototype){
return function(){
for(var i = 0, length = list.length; i < length; i++)
this[list[i].key] = list[i].callback;
result = prototype.apply(this, arguments);
while(i--)
delete this[list[i].key];
return result;
}
})(prototype[key]);
default:
constructor.prototype = prototype;
break;
}
return constructor;
};

Enjoy my code and have fun with JavaScript!

4 comments:

Unknown said...

Wow, I don't really understand what's going on but nice job!

Unknown said...

Very nice, only one limitation I can see: the protected members don't get injected in to the constructor, so they're not accessible until after the object has been created.

Still extremely useful though. Well done!

Anonymous said...

Man, my head is going to explode!

Isn't there a way you can create protected methods using instanceof to check the caller?

michele said...

I'm trying to achieve protected methods in a parasitic inerhitance paradigm: some examples here
http://exsl.byethost24.com