Wednesday, October 31, 2012

Early Mixins, Late Mixins

Early Mixins, Late Mixins:
In JavaScript, the language supplies us with several code reuse patterns. Prototype chaining gives us the primary single inheritance mechanism. Mixins are also an important pattern when we want one object to “inherit” functionality from multiple objects. There are several ways to implement the mixin process. This article looks at two contrasting implementations: early mixins and late mixins.


Late Binding



Let’s start with a quick review of what late binding gives us.

var adam = {
greet: function() {
return "hello";
}
};

adam.greet(); // "hello"


When we ask adam to greet, the method to be executed is looked up at the time of the call. This is late binding and means if we redefine the greet method, subsequent calls will use the new definition.

adam.greet = function(name) {
return "hello" + (name ? (", " + name) : "");
};

adam.greet("world"); // "hello, world";


This ability to redefine a method is valuable because it makes it possible for plugins to modify the behaviour of an existing code library.



Late Binding and Prototype Chaining



How does late binding work with prototype chaining? Let’s start with a fresh example. First we’ll make a new object adam who introduces himself.

var adam = {
_name: "Adam";
greet: function() {
return "hello. I am " + this._name;
}
};


Someone named Adam seems like a suitable prototype for all existing people. We can make a constructor function to produce more people.

function Person(name) {
this._name = name;
}
Person.prototype = adam;

var eve = new Person("Eve");
eve.greet(); // "hello. I am Eve"


Thanks to the combination of late binding and the prototype chain, a change to adam will result in a change to eve instantly. The following could be some plugin for the library that provides the adam object. This redefinition could happen at runtime due to user interaction.

adam.greet = function(name) {
return "hello" + (name ? (", " + name) : '') + ". I am " + this._name;
};

eve.greet("world"); // "hello, world. I am Eve"




Early Mixins



Another way to create a reusable bunch of code as a library is to provide one object who’s properties can be mixed into another object. This is usually done with an early mixin implementation.

First, a generic function that can mix properties from one object into another object.

function earlyMixin(sink, source) {
for (var property in source) {
sink[property] = source[property];
}
}


Now a object containing methods that would be useful on another object. Here is a library called Speeches.JS.

var speeches = {
greet: function() {
return "hello. I am " + this._name;
},
farewell: function() {
return "goodbye. I am " + this._name;
}
};


Since this Speeches.JS library is already written and well unit tested, we’d like to reuse that code in our code.

function Person(name) {
this._name = name;
}
earlyMixin(Person.prototype, speeches);

var adam = new Person('Adam');
adam.greet(); // "hello. I am Adam"


Now suppose we have mixed this speeches object into several other objects (like the Person.prototype object.) Also suppose we want to modify the speeches object at some later time and have all objects who’ve had the speeches object mixed into it be updated. This is what a plugin for Speeches.JS might want to do.

speeches.greet = function(name) {
return "hello" + (name ? (", " + name) : '') + ". I am " + this._name;
};


Unfortunately we cannot do this because the speeches methods have been mixed into the Person.prototype object early (i.e. at the time of mixin.) We still have

adam.greet("world"); // "hello. I am Adam"



Late Mixins



In order to make it possible to modify speeches and have all objects use the modified methods, we need one level of indirection in the mixin process.

function lateMixin(sink, source) {
for (var property in source) {
(function(property) {
sink[property] = function() {
return source[property].apply(this, arguments);
}
}(property));
}
}


Instead of directly borrowing the methods of the source, the sink is given methods that call the methods of source. This means that the method on source are mixed in late because they are looked up when they are called (rather than when they are mixed in.)

Now we can go through the same example with a different result.

var speeches = {
greet: function() {
return "hello. I am " + this._name;
},
farewell: function() {
return "goodbye. I am " + this._name;
}
};

function Person(name) {
this._name = name;
}
lateMixin(Person.prototype, speeches);

var adam = new Person('Adam');
adam.greet(); // "hello. I am Adam"

speeches.greet = function(name) {
return "hello" + (name ? (", " + name) : '') + ". I am " + this._name;
};

adam.greet("world"); // "hello, world. I am Adam"


When adam.greet is called it uses the most recent definition of speeches.greet. Yay! Late mixins have given us the same dynamic power that late binding and the prototype chain give us. This makes a library like Speeches.JS more flexible and opens it up for various kinds of modification at run time.


Not Quite Everything



Late mixins give us some of the power of late binding and the prototype chain but not everything. If we add another method to the speeches object it is not added to the other objects into which speeches has been mixed. There are several ways to accomplish this type of functionality. Something like proxies might be our answer to get closer to real multiple inheritance.

DIGITAL JUICE

No comments:

Post a Comment

Thank's!