Patrones de diseño en JavaScript

En este post, se mostraran algunas de las implementaciones de JavaScript y se explicarán algunos de sus patrones de diseño (para todos los ejemplo realizados se utilizará ecma2016 o 262).

En la actualidad es muy común que como desarrolladores nos preguntemos cuál es una de las mejores formas de implementar un flujo de trabajo utilizando JavaScript, realmente no existe una respuesta concreta a esta pregunta, ya que cada aplicación tiene sus propias necesidades individuales por lo que es necesario pensar cuando consideramos que un patrón nos pueda ofrecer una solución ideal, teniendo lo anterior en cuenta realicé la siguiente pregunta.

:thinking: ¿Qué es un patrón de diseño?

Es la base para la búsqueda de soluciones a problemas muy comunes en el desarrollo de aplicativos y otros ámbitos referentes al diseño de interacciones o interfaces. (Fuente wikipedia).

Se podría resumir de una manera más sencilla como “una manera de resolver una problemática”, un patrón de diseño tiene que cumplir por lo menos con los siguientes objetivos.

  1. Estandarizar el lenguaje entre desarrolladores.
  2. Evitar perder tiempo en soluciones a problemas ya resueltos o conocidos.
  3. Crear código que se pueda reutilizar.

Manos a la obra

Después de un poco de teoría empecemos con lo divertido, hablemos de código :grin:. Existen varios patrones de diseño en este caso solo hablaremos de 3 patrones.

Patrón Object Literals

Tal cual su nombre lo dice este patrón se conforma con la creación de un objeto que prácticamente es un JSON. Una de las bondades de este patrón es que nos permite escribir el código de una manera organizada y no se corrompe el scope(alcance) global con nombres innecesarios, lo cual es una muy buena práctica sobre todo para los proyectos muy grandes.

Como se comenta al principio la notación de este patrón es muy similar a la construcción de un JSON, ya que cuenta con identificadores que permite el acceso al contenido de cada uno de ellos.

// Se inicia la declaracion del patron ObjectLiteral
var MyObjectLiteral = {
	suma: function(valor_a, valor_b) {
		let resultado = valor_a + valor_b;
		console.log("EL resultado de "+ valor_a +"+"+ valor_b +" = "+ resultado);
	},
	resta: function(valor_a, valor_b) {
		let resultado = valor_a - valor_b;
		console.log("EL resultado de"+ valor_a +"-"+ valor_b +" = "+ resultado);
	},
};

// invocando a la funcion suma
MyObjectLiteral.suma(10, 90);
// invocando a la funcion resta
MyObjectLiteral.resta(90, 30);

Como podemos ver en el ejemplo anterior tenemos el ejemplo base del patrón, :confounded:hey un momento se supone que los ejemplos se escribirían con las nuevas mejoras que nos brinda EcmaScript, tomando en cuenta el comentario realizaremos las mejoras para que nuestro código se vea de la manera siguiente.

// Se inicia la declaracion del patron ObjectLiteral
let MyObjectLiteral = {
	suma(valor_a, valor_b) {
		let resultado = valor_a + valor_b;
		console.log(`EL resultado de ${valor_a} + ${valor_b} = ${resultado}`);
	},
	resta(valor_a, valor_b) {
		let resultado = valor_a - valor_b;
		console.log(`EL resultado de ${valor_a} - ${valor_b} = ${resultado}`);
	},
};

// Destructuración
const { suma, resta } = MyObjectLiteral;
// invocando a la funcion suma
suma(10, 90);
// invocando a la funcion resta
resta(90, 30);

:star_struck:Genial nuestro código ya tiene muchas de las mejoras que ofrece JavaScript entre las mejoras que encontramos son.

Tomando en cuento lo anterior los ejemplo siguientes utilizaran las nuevas mejoras de JavaScript_, es importante mencionar que el patrón Object Literals es la base para el siguiente patrón, ya que lo utilizaremos para el manejo de la lógica.


Patrón Module

Para poder comprender este patrón es necesario entender el siguiente concepto de esta función.

// Módulo anónimo
(() => console.log("Me ejecuto de manera inmediata . . . . 😬"))();

Ese tipo de declaración se conoce como IIFE (Immediately-Invoked-Function-Expressions), y como su nombre lo dice es una función que se ejecuta de manera inmediata. Esta función crea un nuevo scope y genera “privacidad”, sin embargo JavaScript no maneja el concepto de “privacidad”, pero al generar un nuevo scope podemos simularlo, esto se logra envolviendo toda la lógica del aplicativo dentro de un contenedor. La idea es solo retornar las partes que nosotros necesitamos, y dejar las otras partes del código fuera del scope global.

Después de crear el nuevo scope, necesitaremos un namespace para tener acceso al código que el módulo anónimo retorne.

NameSpace : Variable con la que se genera la referencia a nuestro módulo anónimo.

// Modulo anónimo
const MyModule = (() => "Me ejecuto de manera inmediata . . . . 😬")();
// Ejecutamos el módulo anónimo
console.log(MyModule);

Hasta este momento podemos tener acceso a lo que retorne el módulo anónimo.

Con anterioridad se habló de “privacidad” dentro de este patrón, para poder mostrar este concepto funcionando analizaremos el siguiente ejemplo.

// Modulo anónimo
const MyModule = (() => {
	// generamos el objeto que tendra todas las funciones publicas
	let publicFunction = {};
	// esta variable es privada
	let total = 0;

	//=========================================//
	// Metodos Privados						   //
	//=========================================//

	privateFunction = () => total * 1000;

	//=========================================//
	// Metodos Publicos	   				       //
	//=========================================//

	publicFunction.suma = (valor_a, valor_b) => {
		const SUMA = valor_a + valor_b;
		total += SUMA;
		console.log(`El resultado de la suma es = ${SUMA}`);
	};

	// Retornamos nuestras funciones publicas
	return publicFunction;
})();

// Ejecutando nuestro metodo publico
MyModule.suma(100, 400); // el resultado es 500

// Intentando acceder a nuestra funcion privada
MyModule.privateFunction(); // esto nos manda un error

Como se muestra en el ejemplo generamos un módulo el cual nos permite realizar una suma sin embargo a simple vista podemos ver un código común y corriente, lo interesante es que ya se está manejando el concepto de “privacidad”, y no podemos tener acceso al método privateFunction, pero si al método suma

Esto se debe a que solo estamos retornando todo lo que contenga la variable publicFunction y lo que no se encuentre dentro de ésta será privado por lo que solo se tendrá acceso dentro del scope de nuestro módulo :scream_cat::smiley_cat:.

La pregunta que te puedes estar realizando hasta este momento es ¿Cómo consulto la respuesta de privateFunction?, para esto tendríamos que generar una función pública que obtenga el resultado que retorna “privateFunction”, como se muestra a continuación.

// Modulo anónimo
const MyModule = (() => {
	// generamos el objeto que tendra todas las funciones publicas
	let publicFunction = {};
	// esta variable es privada
	let total = 0;

	//=========================================//
	// Metodos Privados						   //
	//=========================================//

	privateFunction = () => total * 1000;

	//=========================================//
	// Metodos Publicos	   				       //
	//=========================================//

	publicFunction.suma = (valor_a, valor_b) => {
		const SUMA = valor_a + valor_b;
		total += SUMA;
		console.log(`El resultado de la suma es = ${SUMA}`);
	};

	publicFunction.getPrivateFunction = () => console.log(`Valor de privateFunction => ${privateFunction()}`);

	// Retornamos nuestras funciones publicas
	return publicFunction;
})();

// Ejecutando nuestro metodo publico
MyModule.suma(100, 400); // el resultado es 500

// Consiguiendo el valor de private function
MyModule.getPrivateFunction(); // esto nos regresa 500000

// Intentando acceder a nuestra funcion privada
MyModule.privateFunction(); // esto nos manda un error

Podemos ver que este patrón nos permite manipular el concepto de “privacidad”, el cual resulta muy útil cuando no queremos que toda la funcionalidad del aplicativo esté expuesta.

Existen diversas maneras de poder escribir el código del patrón module, algunas de ellas se muestran en el siguiente ejemplo.

Nota : en este patrón es común ver que todos los métodos privados cuentan con el prefijo _. Tomando en cuenta la recomendación anterior nuestro código de ejemplo quedaría de la manera siguiente.

// Modulo anónimo
const MyModule = (() => {
	// generamos el objeto que tendra todas las funciones publicas
	let publicFunction = {};
	// esta variable es privada
	let _total = 0;

	//=========================================//
	// Metodos Privados						   //
	//=========================================//

	_privateFunction = () => _total * 1000;

	//=========================================//
	// Metodos Publicos	   				       //
	//=========================================//

	publicFunction.suma = (valor_a, valor_b) => {
		const SUMA = valor_a + valor_b;
		_total += SUMA;
		console.log(`El resultado de la suma es = ${SUMA}`);
	};

	publicFunction.getPrivateFunction = () => console.log(`Valor de privateFunction => ${_privateFunction()}`);

	// Retornamos nuestras funciones publicas
	return publicFunction;
})();

// Ejecutando nuestro metodo publico
MyModule.suma(100, 400); // el resultado es 500

// Consiguiendo el valor de private function
MyModule.getPrivateFunction(); // esto nos regresa 500000

// Intentando acceder a nuestra funcion privada
MyModule.privateFunction(); // esto nos manda un error

Patrón Prototype

Este patrón de diseño tiene como finalidad crear nuevos objetos duplicándolos, clonando una instancia creada previamente, en resumen se podría decir que el uso de este patrón es lo más cercano a POO (Programación Orientada a Objetos), con los nuevas funciones de EcmaScript esto puede armarce usando class, extends, etc.

Otra de las características es que todos los objetos en JavaScript cuentan con la propiedad proto, lo cual facilita crear nuevas funciones para clases ya existentes, prácticamente este patrón es la onda jejeje.

Para poder comprender mejor este patrón vamos a crear un ejemplo usando prototype y otro utilizando las nuevas funciones de EcmaScript.

Paso 1:

Creando la clase Persona usando prototype y Class

// Creamos la clase usando prototype

/**
 * Constructor
*/
function Persona(nombre, apellido) {
	this.apellido = apellido;
	this.nombre = nombre;
}

/**
 * Permite obtener el apellido
 *
 * @return void.
 */
Persona.prototype.getApellido = function() {
	console.log(`Mi appelido es ${this.apellido}`);
}; 

/**
 * Permite obtener el Nombre
 *
 * @return void.
 */
Persona.prototype.getNombre = function() {
	console.log(`Mi Nombre es ${this.nombre}`);
};

// Generamos la instancia de la clase
const persona = new Persona("Clark", "Kent");
// invocamos los metodos
persona.getNombre(); // Mi nombre es Kent
persona.getApellido(); // Mi apellido es Clarck

// ========================================================================= //

// Creando la clase usando la palabra reservada class

class Persona {
	/**
	 * Constructor
	*/
	constructor(){}

	/**
	 * Permite obtener el apellido
	 *
	 * @return void.
	 */
	getApellido() {
		console.log(`Mi appelido es ${this.apellido}`);
	}
	/**
	 * Permite obtener el Nombre
	 *
	 * @return void.
	 */
	getNombre() {
		console.log(`Mi Nombre es ${this.nombre}`);
	}
}

// Generamos la instancia de la clase
const persona = new Persona("Clark", "Kent");
// invocamos los metodos
persona.getNombre(); // Mi nombre es Kent
persona.getApellido(); // Mi apellido es Clarck

Paso 2:

Para el manejo de la herencia en EmacScript 2016 ya se puede utilizar extends, en versiones anteriores la herencia se manejaba de una manera más compleja.

// Creamos la clase y una herencia usando prototype 

/**
 * Constructor
*/
function PersonaUsandoPrototype(nombre, apellido) {
	this.apellido = apellido;
	this.nombre = nombre;
}

/**
 * Permite obtener el apellido
 *
 * @return void.
 */
PersonaUsandoPrototype.prototype.getApellido = function() {
	console.log(`Mi appelido es ${this.apellido}`);
}; 

/**
 * Permite obtener el Nombre
 *
 * @return void.
 */
PersonaUsandoPrototype.prototype.getNombre = function() {
	console.log(`Mi Nombre es ${this.nombre}`);
};

// Generando herencia

/**
 * Constructor
*/
function SuperHeroUsandoPrototype(nombre, apellido, powers) {
	// Se genera el llamado al constructor de la clase persona
	PersonaUsandoPrototype.call(this, nombre, apellido);
	this.powers = powers;
}

// generamos la herencia
SuperHeroUsandoPrototype.prototype = Object.create(PersonaUsandoPrototype.prototype);
/** 
 * Permite comnseguir los datos de nuestro heroe
 *
 * return void.
 */
SuperHeroUsandoPrototype.prototype.getSuperHero = function() {
	// invocamos los metodos de las clase person
	this.getNombre();
	this.getApellido();
	console.log(`Mi super poder es ${this.powers}`);
};

// Generamos la instancia de la clase
const HERO_PROTO = new SuperHeroUsandoPrototype("Jorge", "Mendez", "Programar como Loco 🤪");
// invocamos los métodos
HERO_PROTO.getSuperHero();
// Mi nombre es Jorge
// Mi apellido es Mendez
// Mi super poder es Programar como Loco 🤪

// ========================================================================= //

// Creando la clase y herencias utilizando las class y extends

class Persona {
	/**
	 * Constructor
	*/
	constructor(nombre, apellido) {
		this.nombre = nombre;
		this.apellido = apellido;
	}

	/**
	 * Permite obtener el apellido
	 *
	 * @return void.
	 */
	getApellido() {
		console.log(`Mi appelido es ${this.apellido}`);
	}
	/**
	 * Permite obtener el Nombre
	 *
	 * @return void.
	 */
	getNombre() {
		console.log(`Mi Nombre es ${this.nombre}`);
	}
}

// Generando la herencia

class SuperHero extends Persona {
	/**
	 * Constructor
	*/	
	constructor(nombre, apellido, powers) {
		// llamando al constructor de la clase persona
		super(nombre, apellido);
		this.powers  = powers;
	}
	/** 
	 * Permite comnseguir los datos de nuestro heroe
	 *
	 * return void.
	 */
	getSuperHero() {
		// invocamos los metodos de las clase person
		this.getNombre();
		this.getApellido();
		console.log(`Mi super poder es ${this.powers}`);
	};
}

// Generamos la instancia de la clase
const heroe = new SuperHero("Jorge", "Mendez", "Programar como Loco 🤪");

heroe.getSuperHero(); 
// Mi nombre es Jorge
// Mi apellido es Mendez
// Mi super poder es Programar como Loco 🤪

El manejo del patrón prototype es era lo más cercano a POO actualmente con las mejoras que a tenido JavaScript ya contamos con un POO mas robusto, pero si recuerdan anteriormente comentamos que todos los objetos de JavaScript cuentan con la propiedad proto, por lo que podríamos agregar nuevas funcionalidades a objetos ya establecidos, por ejemplo podríamos crear una nueva función para el Objeto String.

String.prototype.cleanString = function() {
	const STRING = this.toLowerCase().split("");
	const SPECIAL = {
		"á" : "a", "é" : "e", "í" : "i",
		"ó" : "o", "ú" : "u", "ñ" : "n",
	};
	let request = STRING.reduce((newString, item) => {
		let char = SPECIAL[item] || item;
		const ASCII = char.charCodeAt();
		let request = (ASCII === 32 || (ASCII >= 48 && ASCII <= 57) || (ASCII >= 97 && ASCII <= 122)) ? char : "";
		return `${newString}${request}`;
	}, "");
	request = request.split(" ")
		.filter(Boolean)
		.join("-")
	return request;
};

const CLEAN = "Jorge Méndez Ortega ^$#%#$%^#%$&*%&^&".cleanString();
console.log(CLEAN); // jorge-mendez-ortega

Conclusión

Los patrones de diseño son un camino para solucionar una problemática de maneras distintas, pero siempre obtendremos una solución que se podrá adaptar a nuestro aplicativo. Se podría decir que para construir nuestro aplicativo no existe un camino correcto, pero sí un patrón que nos facilite acercarnos al camino que buscamos.