Mixins en JavaScript
Los mixins en JavaScript son un patrón de diseño ampliamente utilizado que te permite realizar programación horizontal disponibilizando funciones a través de varias clases u objetos. Vamos a ver un poco más a detalle este patrón con un ejemplo práctico.
Escenario
Supongamos que tenemos una clase que valida si un número es dígito.
Si aún no has tenido contacto con las clases de JavaScript, te recomendamos ver nuestro artículo Clases en JavaScript e Introducción a la Programación Orientada a Objetos.
class Digits {
RegExpr = /^\d*$/;
messages = {};
constructor(value) {
this.value = value;
}
isValid() {
if (!this.value.match(this.RegExpr)) {
this.messages.notDigits = 'The input must contain only digits';
return false;
}
return true;
}
getMessages() {
return this.messages;
}
}
Para verificar si un string es numérico podríamos hacer algo como lo siguiente:
const digits = new Digits('a');
digits.isValid();
digits.getMessages(); // {notDigits: 'The input must contain only digits'}
Ahora bien, supongamos que tenemos otra clase que valida una cadena alfanumérica.
class Alnum {
RegExpr = /^(\d|[a-zA-Z]|\s)*$/;
messages = {};
constructor(value) {
this.value = value;
}
isValid() {
if (!this.value.match(this.RegExpr)) {
this.messages.notAlnum = 'The input contains characters which are non alphabetic and no digits';
return false;
}
return true;
}
getMessages() {
return this.messages;
}
}
Para verificar si un string es alfanumérico podríamos hacer algo como lo siguiente:
const alnum = new Alnum('añ/45@');
alnum.isValid();
alnum.getMessages();
Uso de mixins
En el anterior ejemplo puedes notar que ambas clases utilizan el método getMessages()
. En primera instancia uno podría pensar en crear una clase abstracta y extender estos dos validadores de ella. Sin embargo, este método es un poco más genérico y podría ser utilizado no solo en validadores sino en cualquier otro tipo de objetos. Es por esto, que la solución adecuada es que este comportamiento sea horizontal y no vertical.
Para lograr esto basta con quitar de cada clase el método getMessages()
y crear el siguiente objeto.
const HasMessages = {
getMessages() {
return this.messages;
},
};
Una vez hecho esto debemos asignar al prototype de cada clase dicho objeto.
Object.assign(Alnum.prototype, HasMessages);
Object.assign(Digits.prototype, HasMessages);
Si utilizas módulos ES en tu aplicación esta sentencia podría ir en cada archivo de clase justo antes del export. De esta manera estás asignando dicho método para ser utilizado por ambas clases sin repetir el código y permitiendo que sea utilizado en otro tipo de entidades.
También es posible asignar el mixin en la función constructora. Este método de asignación tiene la ventaja de tener toda la definición de los métodos en un solo lugar.
constructor(value) {
this.value = value;
Object.assign(this, HasMessages);
}
Incluso podemos ir un poco más allá y asignar el método directamente al prototipo (hemos usado puntos suspensivos para omitir el código que no es importante para el ejemplo).
class Alnum {
...
constructor(value) {
this.value = value;
Object.assign(this, HasMessages);
}
...
}
const HasMessageMixin = {
__proto__: {
getMessages() {
return this.messages;
},
},
getMessages() {
return super.getMessages()
}
}