El desarrollo de software es una mezcla de arte y ciencia, y como en toda disciplina, existen metodologías y técnicas que ayudan a estructurar mejor nuestro trabajo. En el mundo de la programación, una de las estrategias más eficaces para escribir código más mantenible, escalable y eficiente es el uso de patrones de diseño. En el contexto de JavaScript, los patrones de diseño son particularmente relevantes debido a la naturaleza dinámica y flexible del lenguaje. En este artículo, exploraremos los patrones de diseño en JavaScript, brindando una guía esencial para desarrolladores que buscan mejorar la estructura de sus proyectos de JavaScript.
Índice de contenido
Toggle¿Qué son los Patrones de Diseño?
Definición de Patrones de Diseño
Los patrones de diseño son soluciones reutilizables a problemas comunes en el diseño de software. Son como plantillas que se pueden aplicar al escribir código para resolver problemas de diseño de manera eficiente.
Importancia de los Patrones de Diseño en JavaScript
JavaScript, al ser un lenguaje flexible y con una sintaxis permisiva, puede llevar a que los desarrolladores escriban código desorganizado o poco estructurado. Los patrones de diseño ofrecen un marco para evitar tales problemas, facilitando la creación de código estructurado JS que sea fácil de entender, probar y mantener.
Clasificación de Patrones de Diseño en JavaScript
Los patrones de diseño se pueden clasificar en diferentes categorías, cada una adaptándose a un tipo específico de problema de diseño. Examinaremos los tres tipos principales: los patrones creacionales, estructurales y de comportamiento.
Patrones Creacionales en JS
Los patrones creacionales se enfocan en la forma en que se crean los objetos en JavaScript. Estos patrones buscan abstraer el proceso de instanciación de un objeto para hacer el sistema más independiente de cómo sus objetos son creados y representados.
Factory Method
El patrón Factory Method está diseñado para crear una instancia de una clase a partir de una de varias subclases posibles, dependiendo de la lógica decidida en tiempo de ejecución.
function Developer(name) {
this.name = name;
this.type = "Developer";
}
function Tester(name) {
this.name = name;
this.type = "Tester";
}
function EmployeeFactory() {
this.create = (name, type) => {
return type === "Developer" ? new Developer(name) : new Tester(name);
};
}
const employeeFactory = new EmployeeFactory();
const employees = [];
employees.push(employeeFactory.create("Patrick", "Developer"));
employees.push(employeeFactory.create("John", "Tester"));
Singleton
El patrón Singleton restringe la instanciación de una clase a un solo objeto. Esto es útil cuando exactamente un objeto es necesario para coordinar acciones a través del sistema.
let instance = null;
class Database {
constructor(data) {
if (!instance) {
instance = this;
this.data = data;
}
return instance;
}
}
const mongo = new Database('mongo');
const mysql = new Database('mysql');
console.log(mongo === mysql); // true
Patrones Estructurales en JS
Los patrones estructurales se enfocan en la manera en que los objetos y las clases se componen para formar estructuras más grandes. El propósito de estos patrones es facilitar el diseño al identificar una forma sencilla de realizar las relaciones entre entidades.
Adapter
El patrón Adapter permite a las interfaces incompatibles trabajar juntas. Actúa como intermediario, traduciendo las llamadas de una interfaz en una forma que la otra interfaz puede entender.
class OldCalculator {
constructor() {
this.operations = function(term1, term2, operation) {
switch (operation) {
case 'add':
return term1 + term2;
case 'sub':
return term1 - term2;
default:
return NaN;
}
};
}
}
class NewCalculator {
constructor() {
this.add = function(term1, term2) {
return term1 + term2;
};
this.sub = function(term1, term2) {
return term1 - term2;
};
}
}
class CalcAdapter {
constructor() {
const newCalc = new NewCalculator();
this.operations = function(term1, term2, operation) {
switch (operation) {
case 'add':
return newCalc.add(term1, term2);
case 'sub':
return newCalc.sub(term1, term2);
default:
return NaN;
}
};
}
}
Decorator
El patrón Decorator proporciona una manera de añadir nuevas responsabilidades a los objetos de manera dinámica sin alterar su estructura.
class Book {
constructor(title, author, price) {
this._title = title;
this._author = author;
this.price = price;
}
getPrice() {
return this.price;
}
}
function giftWrap(book) {
book.isGiftWrapped = true;
book.getPrice = function() {
return this.price + 10; // Adding extra cost for gift wrap
};
return book;
}
const myBook = new Book('Clean Code', 'Robert C. Martin', 30);
const giftWrappedBook = giftWrap(myBook);
Patrones de Comportamiento en JS
Los patrones de comportamiento se enfocan en la comunicación entre objetos, especificando cómo los objetos interactúan y se reparten la responsabilidad.
Observer
El patrón Observer facilita un modelo de suscripción donde los objetos pueden suscribirse a ciertos eventos de otro objeto y ser notificados cuando dicho evento ocurre.
class Product {
constructor() {
this.price = 0;
this.actions = [];
}
setBasePrice(val) {
this.price = val;
this.notifyAll();
}
register(observer) {
this.actions.push(observer);
}
unregister(observer) {
this.actions = this.actions.filter(el => !(el instanceof observer));
}
notifyAll() {
return this.actions.forEach((el) => el.update(this));
}
}
class Fees {
update(product) {
product.price = product.price * 1.2;
}
}
class Profit {
update(product) {
product.price = product.price * 2;
}
}
const product = new Product();
const fees = new Fees();
const profit = new Profit();
product.register(fees);
product.register(profit);
product.setBasePrice(100);
Chain of Responsibility
El patrón Chain of Responsibility permite que varios objetos procesen una solicitud sin necesidad de que el emisor de la solicitud conozca la cadena concreta de manejo.
class CumulativeSum {
constructor(initialValue = 0) {
this.sum = initialValue;
}
add(value) {
this.sum += value;
return this;
}
}
// Uso
const sum1 = new CumulativeSum();
console.log(sum1.add(10).add(2).add(50).sum); // 62
const sum2 = new CumulativeSum(10);
console.log(sum2.add(10).add(20).add(5).sum); // 45
Buenas Prácticas en el Uso de Patrones de Diseño en JavaScript
No Sobreutilizar los Patrones
Es importante no caer en la tentación de utilizar patrones de diseño en todas las situaciones. No todos los problemas requieren un patrón de diseño, y en algunos casos, su uso podría complicar más la solución.
Código Limpio y Mantenible
Los patrones de diseño son una herramienta para escribir código más limpio y mantenible. Específicamente, el código estructurado JS debe ser fácil de entender y cambiar a lo largo del tiempo.
Familiarizarse con los Patrones
Para utilizar efectivamente los patrones de diseño, es fundamental comprender en profundidad cada patrón y reconocer los problemas que están destinados a resolver.
Refactoring
La aplicación de patrones de diseño puede ser un paso efectivo en el proceso de refactoring. Pueden ayudar a transformar código mal estructurado en una arquitectura más limpia y comprensible.
Conclusión
Los patrones de diseño en JavaScript son herramientas esenciales para cualquier desarrollador que busque escribir código robusto y escalable. Aprendiendo y aplicando estos patrones, los desarrolladores pueden mejorar significativamente la calidad de sus aplicaciones y trabajar de manera más efectiva en proyectos complejos. Sin embargo, es crucial recordar que cada patrón debe usarse con discernimiento y siempre en función de las necesidades específicas del proyecto. Con práctica y experiencia, los patrones de diseño se convertirán en una parte valiosa del repertorio de cualquier desarrollador JavaScript.