Mixin
Cuando tenemos una serie de clases de TypeScript las cuales no tienen ninguna relación de herencia entre ellas pero necesitamos que tengan comportamientos comunes, crearemos mixin , para los que hayan visto PHP es algo así como los traits, estos mixin nos permiten utilizar las interfaces como clases para poder intentar tener un sucedaneo de herencia multiple, la cual nos permitirá modificar el comportamiento de las clases para poder implementar estos "contratos".
Esto es un mixin en TypeScript a continuación vamos a explicar paso a paso el siguiente ejemplo:
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
}
// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
class SmartObject implements Disposable, Activatable {
// Este constructos lo que hará será que mostrará por pantalla
// los estados isActive y isDisposed cada 500 ms
constructor() {
setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
}
interact() {
this.activate();
}
// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
}
applyMixins(SmartObject, [Disposable, Activatable]);
let smartObj = new SmartObject();
// esto generará una interactuación cada segundo cambiando el estado de
// `false - false` a `true - true` la primera vez,
// luego como la variable ya valdría `true - true`
// simplemente se dedicaría a mostrarla cada segundo
setTimeout(() => smartObj.interact(), 1000);
////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
En este trozo de código podremos apreciar que hay dos clases distintas y que cada una de ellas se enfoca en una actividad o capacidad distinta. Más adelante mezclaremos estas clases juntas en otra clase para poder obtener ambas capacidades.
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
}
// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
Lo siguiente que haremos es crear una clase la cual implementará la combinación entre las dos clases previamente vistas.
class SmartObject implements Disposable, Activatable {
Lo primero que podreis apreciar es que en vez de utilizar extends
estamos utilizando implements
, esto es debido a que en TypeScript no existe la herencia multiple y utilizando la palabra reservada implements
trata a las clases que se está implementando como interfaces que deben ser cumplidas. Esto implica que la clase que acabamos de crear debe tener los mismo métodos que las dos "interfaces"
// Implementamos Disposable
isDisposed: boolean = false;
dispose: () => void; // esto es lo mismo que declarar una función vacía
// Implementamos Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
El siguiente paso es crear una función la cual nos permitirá realizar el mixin de cada una de las propiedades de las clases en el objeto que deseemos
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}
En el siguiente paso lo que se hace es realmente implementar el mixin de las clases:
applyMixins(SmartObject, [Disposable, Activatable]);