Patrones de diseño en JavaScript

Los patrones de diseño son soluciones probadas para problemas comunes en el desarrollo de software.

Patrón Módulo: Encapsulamiento y Organización

El patrón módulo se utiliza para encapsular código y organizarlo de una manera limpia.

const myModule = (function () {
  const privateVariable = 'I am private';

  function privateMethod() {
    console.log(privateVariable);
  }

  return {
    publicMethod: function () {
      privateMethod();
    }
  };
})();

myModule.publicMethod(); // I am private

Patrón Observador: Comunicación entre objetos

El patrón observador permite que un objeto notifique a otros objetos sobre cambios en su estado.

function Observer() {
  this.update = function () {
    console.log('Observer updated!');
  };
}

function Subject() {
  this.observers = [];
  this.addObserver = function (observer) {
    this.observers.push(observer);
  };
  this.notify = function () {
    this.observers.forEach(observer => observer.update());
  };
}

const observer1 = new Observer();
const subject = new Subject();
subject.addObserver(observer1);
subject.notify(); // Observer updated!

Patrón Singleton: Una única instancia

El patrón singleton asegura que una clase tenga una única instancia y proporciona un punto de acceso global a ella.

const Singleton = (function () {
  let instance;

  function createInstance() {
    return { message: 'I am the instance' };
  }

  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

Patrón Estrategia: Flexibilidad de algoritmos

El patrón estrategia permite seleccionar un algoritmo en tiempo de ejecución.

function StrategyA() {
  this.execute = function (a, b) {
    return a + b;
  };
}

function StrategyB() {
  this.execute = function (a, b) {
    return a * b;
  };
}

function Context(strategy) {
  this.strategy = strategy;
  this.setStrategy = function (strategy) {
    this.strategy = strategy;
  };
  this.executeStrategy = function (a, b) {
    return this.strategy.execute(a, b);
  };
}

const context = new Context(new StrategyA());
console.log(context.executeStrategy(3, 4)); // 7
context.setStrategy(new StrategyB());
console.log(context.executeStrategy(3, 4)); // 12

Patrón Cadena de Responsabilidades: Desacoplar emisores y receptores

El patrón cadena de responsabilidades permite a múltiples objetos manejar una solicitud, desacoplando emisores y receptores.

function RequestHandler() {
  this.nextHandler = null;
  this.setNextHandler = function (handler) {
    this.nextHandler = handler;
  };
  this.handle = function (request) {
    if (this.nextHandler) {
      return this.nextHandler.handle(request);
    }
    return 'Request handled by default';
  };
}

const handler1 = new RequestHandler();
const handler2 = new RequestHandler();
const handler3 = new RequestHandler();
handler1.setNextHandler(handler2);
handler2.setNextHandler(handler3);

console.log(handler1.handle('some request')); // Request handled by default

Tabla Comparativa de Pros y Contras de Patrones de Diseño

PatrónProsContras
MóduloEncapsulación de código, Mejora la organización del códigoPuede volverse complejo con módulos anidados
ObservadorPromueve la comunicación entre objetos, Facilita la implementación del patrón de notificaciónPuede resultar en dependencias no deseadas, Difícil de depurar
SingletonGarantiza una única instancia, Ahorra recursos al evitar múltiples instanciasDifícil de testear, Puede violar el principio de responsabilidad única
EstrategiaPromueve la flexibilidad y reutilización del código, Permite cambiar algoritmos en tiempo de ejecuciónPuede resultar en un alto número de clases, Puede ser complejo de administrar
Cadena de ResponsabilidadesDesacopla el emisor del receptor, Fácil de extenderPuede ser difícil de seguir el flujo de manejo de solicitudes, Difícil de depurar