Wednesday, August 24, 2011

Yet another way for Javacript class inheritance, sort of

    Recently, I'm so interested in Node.js and i'm planning to do something cool with that new stuff. I have .NET background so i would expect I can write Javascript in OOP style or at least I want to create classes with inheritance support. Again, as usual, I did some googling :D. That leads me to following blog:
http://ejohn.org/blog/simple-javascript-inheritance/
    That is an elegant solution and it works. However, there is a drawback: you can only inherit from object not from type which i want to do. I mean if you want to create a class Pet and another class named Cat inherit from Pet, using this approach, you have to create an object name Pet and extend that pet object to make another Cat object.
    I try to find other solution and find out some magic of using Javascript prototype which leads to my solution. First, we all know that everything in Javascript is object. Second we can create a function and it would be a constructor for any desire class.
    For example, we can create a class Pet like below:

var Pet = function(name, species){
    this.Name = name;
    this.Species = species;        
};

// Define abstract method
Pet.prototype.Shout = function() {
    throw "Not implemented exception"; // Like .NET huh?
};

// and then we can create an object of type Pet:
var pet = new Pet('Scoobydoo', 'Dog');

    Alright, what we want to do is some how creating a function Dog that can create dogs inherit from Pet, dogs should also have Name and Species property.
    I notice that in javascript, we can define methods, properties for "any object" at runtime using Object.defineProperty method. This ability is like extension methods in .NET but it's cooler since we can define the extensions at runtime.
    Okey, go back, with this idea in mine, i would implement the "inherit" method like below:
Object.defineProperty(Object.prototype, "Inherit", {
    enumerable: false,
    value: function(from) {
        if (typeof (from) != 'function') {
            throw 'Can inherit from a class function only';
        }
        if (typeof (this) != 'function') {
            throw 'Can call method on a class function only';
        }
        this.prototype = new from();        
        this.prototype._base = from.prototype;
    }
});

    So I can create the Dog class function like:
var Dog = function(name) {
    Dog.prototype = new Pet(name, 'Dog');
};

// Override the abstract method
Dog.prototype.Shout = function(){
    console.log('Woof!!!');
};

    If you notice, you can see that inside the "Inherit" method, i define a property name _base. That means the new type that inherits from provided type can access the base implementation:
Dog.prototype.Bark = function() {
    this._base.Shout();
};

    Run this code will throw the "Not implemented exception" because of the Pet base method. We can redefine the Bark method to call the override Shout method:
Dog.prototype.Bark = function() {
    this.Shout();
};

    In summary, to be able to call method "Inherit" you need to defined base type, then define a derived type with a constructor and finally call this method. I would make another utility that can be reused to completly inherit from a given type include it's default constructor:
// Call this method to completely inherit from a type include the base constructor
function inheritFrom(type) {
    if (typeof (type) != 'function') {
        throw 'type must be a function';
    }
    
    // constructor with more than 3 params is not a good practice
    // in that case, param3 should be an object that contains the rest params
    var theClass = function(param1, param2, param3){
        type.call(this, param1, param2, param3);
    };    
    
    theClass.Inherit(type);
    return theClass;
}

    There is 1 interesting point about this method is that it returns a function definition which is an object in Javascript. So the overall demo would look like:
// PET: base class with a constructor require 2 params
var Pet = function(name, species){
    this.Name = name;
    this.Species = species;    
};
// Define abstract method
Pet.prototype.Shout = function() {
    throw "Not implemented exception"; // Like .NET huh?
};

// DOG: directly inherit from Pet, include constructor
var Dog = inheritFrom(Pet);
Dog.prototype.Shout = function(){
    console.log('Woof!!!');
};
Dog.prototype.Bark = function() {
    this.Shout();
};

// FISH: define another constructor which call base constructor inside, then inherit from the base type
var Fish = function(name) {
    Pet.call(this, name, 'Fish');
};
Fish.Inherit(Pet);
Fish.prototype.Shout = function(){
    console.log('!!!!!!!!!!!!!!!!!');
};


// CAT: directly inherit from Pet, include constructor
var Cat = inheritFrom(Pet);


var pet1 = new Dog('Scoobydoo', 'Dog');     // call inherit constructor
var pet2 = new Fish('Nemo');                // call custom constructor without specify species
var pet3 = new Cat('Garfield', 'Cat');      // call inherit constructor

pet1.Shout();              // Output 'Woof!!!'
pet1.Bark();               // Output 'Woof!!!'
console.log(pet1.Name);    // Output 'Scoobydoo'
console.log(pet1.Species); // Output 'Dog'


pet2.Shout();              // Output '!!!!!!!!!!!!!!!!!'
pet2.Bark();               // ERROR: Object has no method 'Bark'
console.log(pet2.Name);    // Output 'Nemo'
console.log(pet2.Species); // Output 'Fish'


pet3.Shout();              // ERROR: Not implemented exception
console.log(pet3.Name);    // Output 'Garfield'
console.log(pet3.Species); // Output 'Cat'

Cheers, comments are welcome!!!

0 comments:

Post a Comment