Wednesday, October 22, 2008

a new Relator object plus unshared private variables

This is a Robert Nyman's post dedicated reply, about JavaScript inheritance and alternatives and private variables.

First of all, I would like to say thanks to Robert for both interesting articles He is writing about JS inheritance, and a link to a personal comment which aim was to bring there my "old" documentation about classical JavaScript inheritance and usage of prototype, closures, and public, privileged, or private, scope when we create a constructor.

About


Last Rob's post talk about private variables, describing them as shared, if present outside the constructor, and valid only for singleton instances.
This is true, and could cause a lot of headache if we are not truly understanding closures and prototype shared methods behaviour, but there is a way to use this peculiarity about shared private variables to create dedicated private variables.

Before I will write about it, let's look into a generic shared private variable example:

Click = function(){
// closure for private methods / variables

// private shared variable
var _total = 0;

// returned constructor + prototype
function Click(){};
Click.prototype.add = function(){
_total++;
};
Click.prototype.getTotal = function(){
return _total;
};
return Click
}();

var left = new Click,
right = new Click;

left.add();
alert(right.getTotal()); // 1

Above example shows that if a variable is created outside the constructor and prototype shared methods use that variable, every call to one of those method from a generic instance will modify that single private variable, created once inside that closure. To obtain a private variable we need privileged methods:

Click = function(){ // privileged methods
var _total = 0;
this.add = function(){
_total++;
};
this.getTotal = function(){
return _total;
};
};

var left = new Click,
right = new Click;

left.add();
alert(right.getTotal()); // 0

As discussed before in my doc, in robert's posts, and everywhere else in the web, privileged methods create many functions for each instance, and with big projects this is not good for both memory and CPU usage.

new Relator object and unshared private variables


The Relator is an object capable to relate a generic variable with a clear object, without modifying the original variable.
The classic example is this:

var myNum = 123;
Relator.set(myNum).description = "I am number 123";
alert(myNum.description); // undefined
alert(Relator.get(myNum).description); // "I am number 123"

Since with Relator it is possible to create a unique relation between a generic object and a private Object, it was natural for me to think that a private variable could be a Relator like object using this as unique relationship.
The new version of my Relator still respect the precedent API, being a Relator itself, but is now able to return a new object Relator like that could be used, as example, inside a closure.

// new Relator can create a Relator like object with method "$"
PrivateRelator = function(Relator){
// Relator argument is a new Relator like object
// present only in this scope
return {
get:function(what){
var value = Relator.get(what);
return value && value.value;
},
set:function(what, value){
Relator.set(what).value = value;
}
}
}(Relator.$()); // create a new Relator

Relator.set(window).value = "Hello World";
Relator.get(window).value; // Hello world
PrivateRelator.get(window); // undefined
PrivateRelator.set(window, "Hello Private World");
PrivateRelator.get(window); // Hello Private World
Relator.get(window).value; // Hello World

The good part of Relator is that the stored variable, if it is an object, is stored by reference, and we can assign more than a private unique relationship between a single object and our extra informations.
The same pattern could be easily re-adapted to create a shared private variables:

Person = function(){

// private methods
function _getName(){
return _private.get(this).name
};
function _setName(name){
_private.get(this).name = name;
};

// private variable, shared by every function in this scope
var _private = Relator.$();

// constructor + prototype
function Person(){
// bridge to _private shared variable
_private.set(this);
};
Person.prototype.getName = function(){
return _getName.call(this);
};
Person.prototype.setName = function(name){
_setName.call(this, name);
};
return Person;

}();

// extended constructor
function Enhanced(){
// necessary to save
// this instance into _private variable
// accessible only via Person scope
Person.call(this);
};
(function(){ // quick runtime extend
var callee = arguments.callee;
if(this instanceof callee)return;
callee.prototype = Person.prototype;
Enhanced.prototype = new callee;
})();


var me = new Person(),
rob = new Enhanced();

me.setName("Andrea");
rob.setName("Robert");

alert([me.getName(), rob.getName()]);
// Andrea,Robert


Conclusion


Sometime a limitation could be easily managed to obtain desired result.
In this case, thanks to JavaScript closures and its prototype nature, the shared variable become a sort of bridge to obtain private variables for each instance.
It is important to understand that even if we extend the base constructor, every new instance will still persist into original _private variable, inside the Person scope, unless we do not specify a different one, redefining every method that use that variable as well:

(function(_private){
Enhanced = function(){
_private.set(this);
};
(function(){ // quick runtime extend
var callee = arguments.callee;
if(this instanceof callee)return;
callee.prototype = Person.prototype;
Enhanced.prototype = new callee;
})();
Enhanced.prototype.getName = function(){
return _private.get(this).name
};
Enhanced.prototype.setName = function(name){
_private.get(this).name = name;
};
})(Relator.$());


See you soon ;)

3 comments:

Brett said...

Great trick...

And interesting about the Relator... makes me thing of the DOM getUserData()/setUserData() but for any object.

I believe you need to add this line into your previous Relator class, in order for your code to work, no?

$ : function () {return this;},


As I mentioned in another comment to you, my post at http://brettz9.blogspot.com/2009/01/i-came-across-this-very-clever-method.html ("clever method" in the post title refers to your post, not mine!) uses a similar approach to the Relator, but does it inside the class without needing function calls.

My way introduces one public counter variable, but it lets the user access the private instance variables without calling methods (i.e., these are actual private variables, not private methods getting variables). However, these are so private that unless public methods are designed in the original class to share them (thus making them effectively public), even an extending class cannot reach them (perhaps good for restricting setting, but not good to restrict getting!). To get around this in my approach, one can define the extending class within the same closure OR pass the container array "__" into the closure and share that same array with extending functions. In either of these latter cases--as with your approach--the fact that the variables can be both retrieved and set by the extending class means these are effectively protected instance variables.

You might adapt your approach to be able to use something like this (see my code below) to make it more generic:

__.call(this, 'name');
__.call(this, 'name', 'value');

or to be really lazy (but reintroduce memory problems), copy this (as a single) line at the top of each function

var _ = (function (that) {
return function () {__.apply(that, arguments);}
})(this);

and then reference:

_('name');
_('name', 'value');

But this latter way is rather pointless since it introduces the problem we were trying to avoid. (Just including it here for fun...)

Or, finally, simply do this, I think, more ideal way (with no need for extra private instance methods):

var _ = _private.get(this); // Copy in each function
_.name;
_.name = 'value';

So to summarize, I think your approach may be more ideal to create protected instance variables without introducing even a public counter variable as my approach to private and protected variables does--but realizing that these are more like protected instance variables not private instance variables (since they can be overridden), while my approach allows one to use actual variables without function calls and get real instance privacy (not protected), but within the base class only (unless the container array is shared, but then it would also be protected).

(Actually, I believe you might be able to get your approach to restrict setting from inheriting classes too, by setting up a key when Relator.set() is called, based on arguments.callee.caller (or value.constructor), and then define Relator.get to only return a COPY of the variable unless it has the same arguments.callee.caller (or value.constructor) key (in that case, return a reference to the variable, as you do presently). Alternatively, you could make Relator only available within the Base class closure, but then it couldn't be accessed at all by an extending class. I also wonder what you could do with true privacy by extending Person by calling its constructor too--haven't looked into it...)


Here's your code adapted as described above:

Person = function(){

// private methods
function _ (v, val){
if (val) {
_private.get(this)[v] = val;
}
return _private.get(this)[v];
};

// private variable, shared by every function in this scope
var _private = Relator.$();

// constructor + prototype
function Person(){
// bridge to _private shared variable
_private.set(this);
};
Person.prototype.get = function(name){
return _.call(this, name);
};
Person.prototype.set = function(name, val){
_.call(this, name, val);
};
return Person;

}();

// Should rename _private as _protected I think, unless you can change Relator.set() as I suggest above)
(function(_private){
Enhanced = function(){
_private.set(this);
};
(function(){ // quick runtime extend
var callee = arguments.callee;
if(this instanceof callee)return;
callee.prototype = Person.prototype;
Enhanced.prototype = new callee;
})();
Enhanced.prototype.get = function(v){
return 'My name is '+_private.get(this)[v]; // Let's do some enhancing!
};
Enhanced.prototype.set = function(v, val){
_private.get(this)[v] = val;
};
})(Relator.$());

var me = new Person();
me.set('name', 'Brett');

var e = new Enhanced();
e.set('name', 'Andrea');

alert(e.get('name')); // 'My name is Andrea'
alert(me.get('name')); // 'Brett'

Andrea Giammarchi said...

What a post Brett :-)
I'll check the rest later but so far I can tell you that the dollar function returns a Relator, it is correct, since the Relator is an object able to create another Relator via the function Relator inside its closure ... not sure it is clear, but Relator, so far, works perfectly ;-)

Brett said...

:) When asked to talk for 5 minutes, one U.S. president supposedly said it would take him too long to prepare, but when asked to talk for an hour, he said no problem... Yes, your Relator definitely works--I'm checking back here again because I actually already need it in some code--but the one at http://webreflection.blogspot.com/2008/07/javascript-relator-object-aka.html does not have the $ function inside of it, so it doesn't work unless that is added there. Sorry if I wasn't clear I was referring to the Relator definition, not your usage.