$('someElement')

Friday, March 23, 2007

Multiple inheritance with prototype.js

As I promised way back in my first post, (which was only 3 posts ago so not really that long right? lol), here is a bit of code.

Really, my contribution to this code is almost nothing, but I have changed someone else's code enough to where I think the result could be useful for someone else, so here it is.

The original code is from Kevin Lindsey's blog. He offers up a pretty nice little inheritance model for javascript. Since I am deeply enamored of the prototype.js library, I took it upon myself to make it into a prototype friendly inheritance model.

So, without further ado, here is the code:
UPDATE: 5/4/07 - changed last line... for info, read my comment below this post.

Object.inherit = function(subClass, baseClass, baseFuncName) {
function inheritance() {}
inheritance.prototype = baseClass.prototype;
Object.extend(subClass.prototype, new inheritance());
subClass.prototype.constructor = subClass;
if (!baseFuncName || baseFuncName == "")
baseFuncName = "base";
subClass[baseFuncName] = baseClass.prototype.initialize;
}


What's different?
The bits that I changed were that I use Object.extend() (which is a prototype.js provided function) on the subClass's prototype to enable multiple inheritance scenarios, and I auto map a base() function to the baseClass's constructor (which in the prototype.js world is the 'initialize' function).

What happened to 'superClass'?
I also removed the reference he put in to the 'superClass', as it is not necessary, and even potentially dead wrong to be in there to begin with. I could have left it in, and accommodated for multiple inheritance the same as with the constructors, but its inclusion is fairly puzzling to me in Kevin's version to begin with. If you have a subclass, and you then have a method that tries to access this.superClass.someMethod()... well first of all the superClass does not point to an instance of the base class, but rather directly to its prototype, so at best you're accessing a static method of the base class's prototype. But if the base class takes advantage of true instance member privilege restrictions (i.e. public instance properties and methods are only given to instantiated objects via the constructor call, and are not tacked directly on to the prototype object), then someMethod() may not even exist at the static level. I think I understand what his intention was (accessing the super class methods from within overridden subclass methods), but his technique only takes static methods into account, and not truly instance-only members. (Sorry for that long explanation... hopefully I can clear it up better a little bit further down with some code examples).

And here are the examples:

Basic single inheritance

class1 = Class.create();
class1.prototype = {
initialize: function(name) {
this.name = name;
},
sayName: function() {
alert(this.name);
}
}

class2 = Class.create();
Object.inherit(class2, class1);
Object.extend(class2.prototype, {
initialize: function(name, title) {
//base class construction
class2.base.call(this, name);

this.title = title;
},
sayTitle: function() {
alert(this.title);
}
});

var test1 = new class1('Bob');
var test2 = new class2('Joe', 'Mr.');

test1.sayName(); // alerts Bob
test2.sayName(); // alerts Joe
test2.sayTitle(); // alerts Mr.

One thing to note here is that I used an Object.extend() call after the inherit call. If I didn't do that (and just did 'class2.prototype = {...}'), you would be overwriting the prototype object, thus doing away with the prototype level inheritance that the inherit() function sets up.

Basic multiple inheritance
Sometimes it may be handy to have a derived class draw functionality from more than one base class, but where those two base classes don't inherit from each other.

Using the same two classes from above...

class3 = Class.create();
class3.prototype = {
initialize: function(nickName) {
this.nickName = nickName;
},
sayNickName: function() {
alert(this.nickName);
}
}

class4 = Class.create();
Object.inherit(class4, class1, "class1base");
Object.inherit(class4, class3, "class3base");
Object.extend(class4.prototype, {
initialize: function(name, nickName) {
//base class construction
class4.class1base.call(this, name);
class4.class3base.call(this, nickName);
}
});

var test4 = new class4("Jim", "Jimmy");
test4.sayName(); // alerts Jim
test4.sayNickName(); // alerts Jimmy

In the code above, you can specify what to call each base class's constructor by providing a 3rd argument to Object.inherit().

NOTE: There is a reason that multiple inheritance is not widely supported in many languages. If you do choose to use it, you should be aware of the fact that if class1 and class3 from above both have methods or properties of the same name, whichever one is inherited last will be the only one available in the new class. That is, if class3 also defined a sayName() method, that's the one that would be available in class4.

Static prototype members vs. true instance-only members and more on why I removed 'superClass' from Kevin's implementation


Many people who use prototype.js will write a class definition something like this (which I stuck with for the above basic examples just to keep people comfortable while demonstrating the inheritance model):

someClass = Class.create();
someClass.prototype = {
initialize: function(name) {
this.name = name;
},
sayName: function() {
alert(this.name);
}
}

So, why is this not exactly the most reliable way? If you look inside the sayName function, you'll of course notice that it accesses 'this.name'. That would be completely fine if you could guarantee that sayName will only ever be called from instances of someClass. But written as above, you cannot guarantee that. Take the following code for example:

var test = new someClass("Bob");
test.sayName() // works fine, alerts Bob

// but...
someClass.prototype.sayName(); // alerts undefined

In this simple case, it's not a wholly terrible problem, as it will just alert 'undefined', but what if it was expecting an object:

someClass = Class.create();
someClass.prototype = {
initialize: function(person) {
this.person = person;
},
sayName: function() {
alert(this.person.name);
}
}

Now the attempt at accessing sayName() statically via 'someClass.prototype.sayName()' causes the application to crash with an error saying something like "object has no properties".

So, if we were to use Kevin's original code, which points a 'superClass' variable to the baseClass's prototype object, and we then tried to access any members of the superClass, we could be in for some surprises.

Furthermore, what if we use a class model that allows us to truly limit members to instances only? I.e. what if sayName were not available at all via the static 'someClass.prototype.sayName()' call?

One can achieve this by encapsulating instance-only members inside the constructor:

someClass = Class.create();
someClass.prototype = {
initialize: function(name) {
//public instance properties
this.name = name;

//public instance methods
this.sayName = function() {
alert(this.name);
};
}
}

I hope you can see how in a class model such as above, only instances of someClass will ever even have a sayName method. This is as it should be, as now I can be certain that whenever 'sayName()' is called, 'this.name' will have been set.

So how can you override base class methods and make sure that the original base class method still gets called?


Let's use the above class as the example. Let's say you want to inherit from that class, but you want to define a new sayName() method that, if the name is "Bob" it says "Robert" (because Bob is a very professional person), but for any other name it just calls the original method. We also want to maintain the fact that sayName should be a true instance-only member.

To achieve the above requirement, use the following:

newClass = Class.create();
Object.inherit(newClass, someClass);
Object.extend(newClass.prototype, {
initialize: function(name) {
//base class construction
newClass.base.call(this, name);

//override
var oldSayName = this.sayName.bind(this);
this.sayName = function() {
if (this.name == "Bob")
alert("Robert");
else
oldSayName();
};
}
});

var test = new newClass("Bob");
var test2 = new newClass("Joe");

test.sayName(); // alerts Robert
test2.sayName(); // alerts Joe

Note that we store a reference to the original method and then call it from within the new one. The '.bind(this)' is another helpful thing we get because we use prototype.js. Without that there, the call to the oldSayName() function would not work right. The "this" keyword within that call would refer not to our instance, but to the function itself. The "this" problem has been explained by many others before me, so I leave it to you if you'd like to research that further (as it is beyond the scope of this post).

So there you go. A more robust single inheritance model (than just using Object.extend), that plays well with prototype's Class model, and that supports multiple inheritance if needed. Thanks Kevin for the inspiration, and all the great people who contribute daily to the prototype.js evolution, and of course to Sam for creating it to begin with.

I hope this helps someone. Cheers.

-Ryan Gahl
I work as a professional software engineering consultant, specializing in web-based (inter/intra/extranet) applications, Ajax, and C#. My services can be purchased by calling the following number: 1-262-951-6727
I am becoming an expert engineer.

7 Comments:

  • Great way to have inheritance with prototype.
    I need it so many times and I will try to use it today in my new graphic framework.
    Thanks
    Seb

    By Blogger Sébastien Gruhier, at 12:19 AM, March 30, 2007  

  • You're welcome Seb... thank Kevin Lindsey as well :-)

    By Blogger Ryan Gahl, at 7:52 AM, March 30, 2007  

  • I definitely agree with sgruhier.

    Wouldn't it be a way to improve readability by adding inherits(...) to the function returned by Class.create()?

    Have a look at this: http://pastie.caboo.se/54141

    Any comments?

    Severin

    By Blogger Lantash, at 3:49 AM, April 16, 2007  

  • @Severin: While I understand your desire to do this, it relies on changing the core prototype function. This is something I try to stay away from for maintainability purposes. This would be something to propose in the prototype-core mailing list: http://groups.google.com/group/prototype-core

    Also: Please take note, that since my post I have realized one small mistake I made while fervantly looking for improvements to Kevin's model. The auto-mapping of the "base" method to the prototype object does not play well with N levels of inheritance. I will post the fix above as soon as I have time, but for now... change the last line in Object.inherit to read: subClass[baseFuncName] = baseClass.prototype.initialize;

    And then use Kevin's way to call the base constructors: someClass.base.call(this, arg1, arg1);

    By Blogger Ryan Gahl, at 9:14 AM, April 16, 2007  

  • The only thing I dont like is that you have to keep around these alias pointers to the base/super class functions like oldSayName();

    Can you go into more detail on why I shouldnt use a superClass? I am currently able to instantiate a subclass, have it instantiate its superClass (inside initialize), and then bind(this) to all super methods to ensure that this.name (or any other instance variable) is available when called.

    I havent found this to be true:
    "If you have a subclass, and you then have a method that tries to access this.superClass.someMethod()... well first of all the superClass does not point to an instance of the base class, but rather directly to its prototype, so at best you're accessing a static method of the base class's prototype. "

    >>I can set the superClass as an instance (not the prototype) explicitly in the initialize method of the subclass

    this.superClass = new newClass.superClass(name);
    this.superClass.sayName = this.superClass.sayName.bind(this);
    ...
    this.superClass.sayName();

    I realize that this sets superClass to only be single inheritence, but Im ok with that.

    Am I missing something?

    By Blogger trebe, at 4:02 PM, May 30, 2007  

  • @trebe: I do explain my position on this point in the middle of the post. What you just described is even more verbose than what I do when I need to override a method and still want to call the original implementation (note, there is no need to have those pointers if you don't need them). You're keeping pointers to the old methods just as I am, only you're putting them in a different place, and you're also creating another object instance in order to achieve this. Having said all this, you're method certainly works so I'd say if that's what you like to do then stick with it. That's really the beauty of javascript, after all.

    By Blogger Ryan Gahl, at 8:31 AM, June 01, 2007  

  • There is this library that implements multiple inheritance in JavaScript: Ring.js http://ringjs.neoname.eu/

    You should take a look at it.

    By Anonymous Anonymous, at 3:26 PM, July 16, 2013  

Post a Comment

<< Home