Uno dei punti di forza di MooTools, rispetto ad altri framework javascript, è il suo avanzato paradigma ad oggetti.
MooTools permette infatti di creare nuove classi che ereditano da altre con Class.Extends, come abbiamo già visto in passato, e di utilizzare i cosìddetti Mixin con Class.Implements.
Questi due sistemi ci permettono di evitare duplicazioni inutili di codice, separandolo in blocchi logici ben distinti.
Nei progetti più complessi può succedere di avere molte sottoclassi di specializzazione, che vengono poi utilizzate tramite Implements. Vediamo un esempio pratico:
[javascript]
// CLASSE BASE
var Animal = new Class({
age: 0,
initialize: function(age) {
this.age = age;
}
});
// INTERFACCE
var Moving = new Class({
action: ‘walks’,
act: function() {
return this.name + ” (“+ this.age +”) ” + this.action;
}
});
var Barks = new Class({
sound: ‘woof’,
bark: function() {
return this.sound;
}
});
// CLASSI FINALI
var Cat = new Class({
Extends: Animal,
Implements: Moving,
name: ‘Kitty’
});
var Dog = new Class({
Extends: Animal,
Implements: [Moving, Barks],
name: ‘Spartacus’
});
[/javascript]
E’ chiaro dall’esempio che grazie a MooTools siamo già riusciti ad ottenere qualcosa di molto simile all’ereditarietà multipla. La class Dog infatti eredita i metodi di Animal, Moving e Barks.
Il Problema
Ma cosa succederebbe se, ad esempio, la classe Moving dovesse eseguire delle operazioni al momento dell’inizializzazione?
Facciamo una prova, aggiungendo il costruttore initialize a Moving e creando un istanza di Dog:
[iframe src=”//jsfiddle.net/mtorromeo/V8NWn/embedded/js,result”]
Non è esattamente quello che ci saremmo aspettati.
Innanzitutto il costruttore è stato eseguito ben 3 volte. Questo perché la classe Moving non viene inizializzata insieme all’istanza Dog, bensì ogni volta che una classe la Implementa, al momento della definizione, ed oltre a questo, il metodo initialize di Moving va a rimpiazzare altri eventuali costruttori definiti precedentemente.
Inoltre il metodo initialize di Moving non “vede” i metodi e le proprietà delle altre classi, dato che l’inizializzazione avviene separatamente. Infatti le prime 2 righe risultanti nell’esempio mostrano “undefined (age undefined) walks” perché this.name e this.age non sono visibili, mentre la terza riga mostra “Spartacus (age 0) walks” a dimostrazione che il costruttore di Animal non è stato chiamato e quindi this.age è rimasto al valore predefinito “0”.
La soluzione
Questa problematica mi si è presentata in più di un’occasione ed anche se non è mai stato troppo complicato trovare soluzioni alternative, ho sempre pensato che l’ideale sarebbe stato avere un costruttore anche per le classi utilizzate con Implements.
Oggi ho deciso di creare una soluzione per questa esigenza, migliorando la gestione interna di Implements, così come definita in MooTools:
[code language=”javascript”]
Class.Mutators.Implements = function(items) {
var oldInit = this.prototype.initialize;
var klass = this;
klass.constructors = [];
klass.prototype.initialize = function() {
if (!this._constructed)
this._constructed = true;
else
return this;
oldInit.apply(this, arguments);
if (typeof this.constructor == ‘function’)
this.constructor.apply(this, arguments);
var _arguments = arguments;
klass.constructors.each(function(constructor) {
constructor.apply(this, _arguments);
}, this);
};
Array.from(items).each(function(item) {
var instance = new item;
if (typeof instance.constructor == ‘function’) {
klass.constructors.push(instance.constructor);
delete instance.constructor;
}
for (var key in instance) Class.implement.call(klass, key, instance[key], true);
});
};
[/code]
Con questo pezzo di codice sono andato a ridefinire Implements, facendo in modo che le classi possano definire un metodo constructor che viene chiamato nel modo che ci aspetteremmo, ricevendo gli stessi parametri di initialize.
Vediamo come funziona l’esempio precedente dopo averlo modificato per usare questa nuova versione di Implements:
[iframe src=”//jsfiddle.net/mtorromeo/jurHD/embedded/js,result”]
Ora i costruttori riescono a leggere le proprietà definite nelle altre classi e vengono eseguiti nell’ordine e nel momento corretto.
L’unico difetto è che abbiamo 2 diversi modi di definire i costruttori: initialize e constructor, ma almeno questo non va a intaccare alcuna funzionalità del framework.
Prossimamente provvederò a pubblicare questa soluzione sul MooTools Forge così da renderla facilmente reperibile.