Oltre all'ereditarietà, possiamo sfruttare i mixin come ulteriore approccio alla creazione di classi che riutilizzano funzionalità già presenti in altre classi. In pratica questo approccio si basa sulla possibilità di combinare classi per definire nuovi classi, sopperendo in qualche modo alla mancanza di supporto dell'ereditarietà multipla.
Per fare un esempio, consideriamo le seguenti classi:
class Studente {
nome: string;
cognome: string;
materie: Materie[];
constructor(nome: string, cognome: string) {
this.nome = nome;
this.cognome = cognome;
this.materie = [];
}
}
class Impiegato {
nome: string;
cognome: string;
mansione: string;
constructor(nome: string, cognome: string, mansione: string) {
this.nome = nome;
this.cognome = cognome;
this.mansione = mansione;
}
}
Oltre alla classe Studente
che conosciamo già, abbiamo definito la classe Impiegato
che, rispetto a Studente
, prevede la proprietà mansione
. Possiamo creare una nuova classe che mette insieme (mix in) le caratteristiche della classe Studente
e quelle della classe Impiegato
specificandola nel seguente modo:
class StudenteLavoratore implements Studente, Impiegato {
nome: string;
cognome: string;
materie: Materie[];
mansione: string;
constructor(nome: string, cognome: string, mansione: string, materie: Materie[]) {
this.nome = nome;
this.cognome = cognome;
this.materie = [];
this.mansione = mansione;
}
}
La classe così definita dichiara di implementare le interfacce delle classi Studente
e Impiegato
, ma non è ancora completa. L'effettiva combinazione delle funzionalità deve essere effettuata da una funzione ad hoc come nel seguente esempio:
applyMixins(StudenteLavoratore, [Studente, Impiegato]);
Tale funzione ha il compito di mettere insieme le caratteristiche di Studente
e Impiegato
e assegnarle alla classe StudenteLavoratore
. Una possibile implementazione, proposta dal sito ufficiale di TypeScript, è mostrata di seguito:
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
})
});
}
Essa analizza l'elenco delle classi da combinare (baseCtors
) e aggiunge ciascun membro alla classe di destinazione (derivedCtor
). Come possiamo intuire, il risultato di un mixin non è una classe che mantiene un qualche legame con le classi d'origine. La nuova classe è il risultato della combinazione delle classi d'origine effettuata da una apposita funzione.
Nel team di sviluppo di TypeScript è in discussione l'opportunità di supportare nativamente una vera e propria combinazione delle classi, ma allo stato attuale tale combinazione deve essere effettuata esplicitamente come nell'esempio visto.