Friday, April 23, 2010

JavaScript Design Patterns - Inheritance

(extract from JavaScript Design Patterns book)
Inheritance is a very complex topic in JavaScript, much more so than in any other objectoriented
language. Unlike most other OO languages, where a simple keyword will allow you to
inherit from a class, JavaScript requires a series of steps in order to pass on public members in
the same way.

To further complicate the issue, JavaScript is one of the few languages that uses
prototypal inheritance (we will show you how this is actually a huge benefit).

Design main goal: you want to design your classes in such a way as to reduce the amount of duplicate code and make your objects as loosely coupled as possible.

Inheritance helps with the first of those design principles, and allows you to build upon existing classes and leverage the methods they already have.


Classical Inheritance


JavaScript can be made to look like a classically inherited language. By using functions to declare classes, and the new keyword to create instances, objects can behave very similarly to objects in Java or C++.
This is a basic class declaration in JavaScript:
/* Class Person. */
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}


First create the constructor. By convention, this should be the name of the class, starting
with a capital letter.
Within the constructor, use the this keyword to create instance attributes.
To create methods, add them to the class’s prototype object, as in Person.prototype.getName.

To create an instance of this class, you need only invoke the constructor with the 'new' keyword:

var reader = new Person('John Smith');
reader.getName();
You can then access all instance attributes and call all instance methods.
This is a very simple example of a class in JavaScript.

The Prototype Chain


To create a class that inherits from Person, it gets a little more complex:

/* Class Author. */
function Author(name, books) {
Person.call(this, name); // Call the superclass's constructor in the scope of this.
this.books = books; // Add an attribute to Author.
}
Author.prototype = new Person(); // Set up the prototype chain.
Author.prototype.constructor = Author; // Set the constructor attribute to Author.
Author.prototype.getBooks = function() { // Add a method to Author.
return this.books;
};

Explanation:


First, create a constructor function, as in the previous example.
Within that constructor, call the superclass’s constructor, and pass in the name argument.
This line deserves a little more explanation. When you use the new operator, several things are done for you.

The first is that an empty object is created. The constructor
function is then called with this empty object at the front of the scope chain; the this in each constructor function refers to this empty object.

So to call the superclass’s constructor within Author, you must do the same thing manually. Person.call(this, name) will invoke the Person constructor with that empty object (which we refer to as this) at the front of the scope chain, while passing in name as an argument.

The next step is to set up the prototype chain. Despite the fact that the code used to do this
is fairly simple, it is actually a very complex topic.

As mentioned before, JavaScript has no extends keyword; instead, every object has an attribute named prototype.
This attribute points to either another object or to null. When a member of an object is accessed (as in reader.getName), JavaScript looks for this member in the prototype object if it does not exist in the current object. If it is not found there, it will continue up the chain, accessing each objects’ prototype until the member is found (or until the prototype is null).


This means that in order to make one class inherit from another, you simply need to set the subclasses’s prototype to point to an instance of the superclass. This is completely different from how inheritance works in other languages and can be very confusing and counterintuitive.


In order to have instances of Author inherit from Person, you must manually set Author’s
prototype to be an instance of Person.


The final step is to set the constructor attribute back to Author (when you set the prototype attribute to an instance of Person, the constructor attribute is wiped out).

Despite the fact that setting up this inheritance takes three extra lines, creating an instance
of this new subclass is the same as with Person:
var author = [];
author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']);
author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']);
author[1].getName();
author[1].getBooks();

All of the complexity of classical inheritance lies within the class declaration.
Creating new instances is still simple.



The EXTEND FUNCTION

In order to make the class declaration more simple, you can wrap the whole subclassing process
in a function, called extend. It will do what the extend keyword does in other languages—create
a new object from a given class structure:
/* Extend function. */
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
}
This function does the same things that you have done manually up to this point. It sets the
prototype and then resets the correct constructor. As a bonus, it adds the empty class F into the
prototype chain in order to prevent a new (and possible large) instance of the superclass from
having to be instantiated. This is also beneficial in situations where the superclass’s constructor
has side effects or does something that is computationally intensive. Since the object that gets
instantiated for the prototype is usually just a throwaway instance, you don’t want to create it
unnecessarily.
The previous Person/Author example now looks like this:
/* Class Person. */
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
/* Class Author. */
function Author(name, books) {
Person.call(this, name);   //superclass (Person) is hardcoded == not the best way.
this.books = books;
}
extend(Author, Person);   //EXTEND
Author.prototype.getBooks = function() {
return this.books;
};

Instead of setting the prototype and constructor attributes manually, simply call the
extend function immediately after the class declaration (and before you add any methods to
the prototype). The only problem with this is that the name of the superclass (Person) is hardcoded
within the Author declaration. It would be better to refer to it in a more general way:


/* Extend function, improved. */
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
}

Download it here.
This version is a little longer but provides the superclass attribute, which you can now
use to make Author less tightly coupled to Person. The first four lines of the function are the same as before. The last three lines simply ensure that the constructor attribute is set correctly
on the superclass (even if the superclass is the Object class itself ). This will become
important when you use this new superclass attribute to call the superclass’s constructor:

/* Class Author. */
function Author(name, books) {
Author.superclass.constructor.call(this, name);  //no more hardcoded superclass's name here
this.books = books;
}
extend(Author, Person);
Author.prototype.getBooks = function() {
return this.books;
};

Adding the superclass attribute also allows you to call methods directly from the superclass.

This is useful if you want to override a method while still having access to the superclass’s implementation of it.

For instance, to override Person’s implementation of getName with a new version,
you could use Author.superclass.getName to first get the original name and then add to it:
Author.prototype.getName = function() {
var name = Author.superclass.getName.call(this);
return name + ', Author of ' + this.getBooks().join(', ');
};



Prototypal Inheritance

We’ve found the best way to think about it is to forget everything you know about classes and instances, and think only in terms of objects.
The classical approach to creating an object is to

(a) define the structure of the object, using a class declaration, and
(b) instantiate that class to create a new object.

Objects created in this manner have their own copies of all instance attributes, plus a link to the single copy of each of the instance methods.

In prototypal inheritance, instead of defining the structure through a class, you simply
create an object.

This object then gets reused by new objects, thanks to the way that prototype
chain lookups work.

It is called the prototype object because it provides a prototype for what
the other objects should look like. It is where prototypal inheritance gets its name.

We will now recreate Person and Author USING PROTOTYPAL INHERITANCE:
/* Person Prototype Object. */
var Person = {
name: 'default name',
getName: function() {
return this.name;
}
};

Instead of using a constructor function named Person to define the class structure, Person
is now an object literal. It is the prototype object for any other Person-like objects that you want
to create. Define all attributes and methods you want these objects to have, and give them
default values. For the methods, those default values will probably not be changed; for attributes,
they almost certainly will be:
var reader = clone(Person);
alert(reader.getName()); // This will output 'default name'.
reader.name = 'John Smith';
alert(reader.getName()); // This will now output 'John Smith'.

To create a new Person-like object, use the clone function (we go into more detail about
this function later in the section “The clone Function”). This provides an empty object with
the prototype attribute set to the prototype object. This means that if any method or attribute
lookup on this object fails, that lookup will instead look to the prototype object.
To create Author, you don’t make a subclass of Person. Instead you make a clone:

/* Author Prototype Object. */
var Author = clone(Person);
Author.books = []; // Default value.
Author.getBooks = function() {
return this.books;
}
The methods and attributes of this clone can then be overridden. You can change the
default values given by Person, or you can add new attributes and methods. That creates a new
prototype object, which you can then clone to create new Author-like objects:
var author = [];
author[0] = clone(Author);
author[0].name = 'Dustin Diaz';
author[0].books = ['JavaScript Design Patterns'];
author[1] = clone(Author);
author[1].name = 'Ross Harmes';
author[1].books = ['JavaScript Design Patterns'];
author[1].getName();
author[1].getBooks();

------

Please read more in the book:
Asymmetrical Reading and Writing of Inherited Members
Example shows why you must create new copies of data types that are passed by reference.

You must create new copies of all arrays and objects before you start changing their members.
It is very easy to forget this and modify the value of the prototype object. This should be avoided at all costs; debugging these types of errors can be very time-consuming.
In these situations, you can use the hasOwnProperty method to distinguish between inherited members and the object’s actual members.

------

Sometimes prototype objects will have child objects within them. If you want to override
a single value within that child object, you have to recreate the entire thing.
This can be done by setting the child object to be an empty object literal and then recreating it, but that would mean that the cloned object would have to know the exact structure and defaults for each child object.
In order to keep all objects as loosely coupled as possible, any complex child objects should be created using methods:
var CompoundObject = {
string1: 'default value',
childObject: {
bool: true,
num: 10
}
}

var compoundObjectClone = clone(CompoundObject);
// Bad! Changes the value of CompoundObject.childObject.num.
compoundObjectClone.childObject.num = 5;

// Better. Creates a new object, but compoundObject must know the structure
// of that object, and the defaults. This makes CompoundObject and
// compoundObjectClone tightly coupled.
compoundObjectClone.childObject = {
bool: true,
num: 5
};
In this example, childObject is recreated and compoundObjectClone.childObject.num is
modified. The problem is that compoundObjectClone must know that childObject has two
attributes, with values true and 10. A better approach is to have a factory method that creates
the childObject:
// Best approach. Uses a method to create a new object, with the same structure and
// defaults as the original.
var CompoundObject = {};
CompoundObject.string1 = 'default value',
CompoundObject.createChildObject = function() {
return {
bool: true,
num: 10
}
};
CompoundObject.childObject = CompoundObject.createChildObject();
var compoundObjectClone = clone(CompoundObject);
compoundObjectClone.childObject = CompoundObject.createChildObject();
compoundObjectClone.childObject.num = 5;

Comparing Classical and Prototypal Inheritance

The classical and prototypal paradigms for creating new objects are very different from each
other, and the objects that each one produces behave differently. Each paradigm has its own
pros and cons, which should help you determine which one to use in a given situation.

Classical inheritance is well understood, both in JavaScript and the programmer community
in general. Almost all object-oriented code written in JavaScript uses this paradigm. If you
are creating an API for widespread use, or if there is the possibility that other programmers not familiar with prototypal inheritance will be working on your code, it is best to go with classical.

JavaScript is the only popular, widely used language that uses prototypal inheritance, so odds
are most people will never have used it before. It can also be confusing to have an object with
links back to its prototype object.

Programmers who don’t fully understand prototypal inheritance will think of this as some sort of reverse inheritance, where the parent inherits from its children. Even though this isn’t the case, it can still be a very confusing topic.


But since this form of classical inheritance is only imitating true class-based inheritance, advanced JavaScript programmers need to understand how prototypal inheritance truly works at some point anyway.


Some would argue that hiding this fact does more harm than good.

Prototypal inheritance is very memory-efficient.



Because of the way prototype chain reads members, all cloned objects share a single copy of each attribute and method, until those attributes and methods are written to directly on the cloned object.


Contrast this with the objects created using classical inheritance, where each object has a copy of every attribute (and private method) in memory.


The savings here are enormous. It also seems to be a much more
elegant approach, needing only a single clone function, rather than several lines of incomprehensible syntax such as
SuperClass.call(this, arg)
and
SubClass.prototype = new SuperClass
for each class you want to extend (it is true, however, that some of these lines can, in turn, be condensed into the extend function).

Don’t think that just because prototypal inheritance is simple that it isn’t also complex.

Its power lies in its simplicity.

The decision to use classical or prototypal inheritance probably depends most on how well
you like each paradigm.

Some people seem naturally drawn to the simplicity of prototypal inheritance, while others are much more comfortable in the more familiar classical.

Both paradigms can be used for each pattern described in this book.

We tend toward classical inheritance for the later patterns, to make them easier to understand, but both can be used interchangeably throughout the book (Javascript Design Patterns).

I put them into seperate pages for easier reading and less clutter to this page.
Examples:

Using Classical Inheritance
Using Strict Inheritance (Extend function), please check EXTEND EXAMPLEs from Object Oriented JavaScript book.
Using Prototypal Inheritance
Using Mixin Classes Inheritance (Augment function)



When Should Inheritance Be Used?
Inheritance adds some complexity to your code and makes it harder for JavaScript novices to
understand what it does, so it should only be used in situations where its benefits outweigh
these drawbacks.
Most of the benefits have to do with code reuse. By having classes or objects
inherit from each other, you only have to define a given method once.
By the same token, if you ever have to make changes to this method or track down errors in it, the fact that it is defined in a single location can save you a great deal of time and effort.

Each paradigm also has its own pros and cons. Prototypal inheritance (with the clone function)
is best used in situations where memory efficiency is important.

Classical inheritance (with the extend function) is best used when the programmers dealing with the objects are familiar with how inheritance works in other object-oriented languages.

Both of these methods are well suited to class hierarchies where the differences between each class are slight.

If the classes are very different from each other, it usually makes more sense to augment them with methods from
mixin classes.

You will find that simpler JavaScript programs rarely require this level of abstraction.

It is only with large projects, with multiple programmers involved, that this sort of organization becomes necessary.

No comments:

Post a Comment