Sobes.tech
Middle+

¿Qué patrones de diseño has utilizado en la práctica?

sobes.tech AI

Respuesta de la IA

En la práctica, he aplicado los siguientes patrones de diseño:

  1. Método de Fábrica (Factory Method): Para crear objetos de diferentes tipos sin especificar la clase concreta de creación. Útil cuando se trabaja con diversas fuentes de datos o configuraciones.

    // Ejemplo de uso del método de fábrica
    class DataReader {
      read() {
        throw new Error("Método no implementado");
      }
    }
    
    class FileReader extends DataReader {
      read() {
        console.log("Leyendo datos del archivo...");
      }
    }
    
    class NetworkReader extends DataReader {
      read() {
        console.log("Leyendo datos de la red...");
      }
    }
    
    class DataReaderFactory {
      createReader(type) {
        switch (type) {
          case "file":
            return new FileReader();
          case "network":
            return new NetworkReader();
          default:
            throw new Error("Tipo de lector desconocido");
        }
      }
    }
    
    const factory = new DataReaderFactory();
    const fileReader = factory.createReader("file");
    fileReader.read();
    
  2. Singleton (Singleton): Para garantizar que solo exista una instancia de la clase y proporcionar un punto de acceso global a ella. Se usa a menudo para gestores de configuración o conexiones a bases de datos.

    // Ejemplo de uso del singleton
    class ConfigManager {
      constructor() {
        if (ConfigManager.instance) {
          return ConfigManager.instance;
        }
        this.config = {};
        ConfigManager.instance = this;
      }
    
      setConfig(key, value) {
        this.config[key] = value;
      }
    
      getConfig(key) {
        return this.config[key];
      }
    }
    
    const instance1 = new ConfigManager();
    const instance2 = new ConfigManager();
    
    console.log(instance1 === instance2); // true
    
  3. Observador (Observer): Para implementar la relación "uno a muchos", donde al cambiar el estado de un objeto (emisor), todos los objetos dependientes (suscriptores) son notificados automáticamente. Se usa en sistemas de notificación, interfaces reactivas.

    // Ejemplo de uso del observador
    class Observable {
      constructor() {
        this.observers = [];
      }
    
      subscribe(observer) {
        this.observers.push(observer);
      }
    
      unsubscribe(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
      }
    
      notify(data) {
        this.observers.forEach(observer => observer.update(data));
      }
    }
    
    class Observer {
      update(data) {
        console.log("Actualización recibida:", data);
      }
    }
    
    const observable = new Observable();
    const observer1 = new Observer();
    const observer2 = new Observer();
    
    observable.subscribe(observer1);
    observable.subscribe(observer2);
    
    observable.notify("¡Nuevos datos disponibles!");
    
  4. Estrategia (Strategy): Para definir una familia de algoritmos, encapsular cada uno y hacerlos intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan. Se aplica en diferentes variantes de procesamiento de datos o ejecución de operaciones.

    // Ejemplo de uso de estrategia
    class PaymentStrategy {
      pay(amount) {
        throw new Error("Método no implementado");
      }
    }
    
    class CreditCardPayment extends PaymentStrategy {
      pay(amount) {
        console.log(`Pagando ${amount} usando tarjeta de crédito`);
      }
    }
    
    class PayPalPayment extends PaymentStrategy {
      pay(amount) {
        console.log(`Pagando ${amount} usando PayPal`);
      }
    }
    
    class PaymentContext {
      setStrategy(strategy) {
        this.strategy = strategy;
      }
    
      executePayment(amount) {
        this.strategy.pay(amount);
      }
    }
    
    const context = new PaymentContext();
    context.setStrategy(new CreditCardPayment());
    context.executePayment(100);
    
    context.setStrategy(new PayPalPayment());
    context.executePayment(50);
    
  5. Decorador (Decorator): Para agregar funcionalidad nueva a un objeto de forma dinámica sin modificar su estructura. Envuelve el objeto existente, proporcionando la misma interfaz que el objeto envuelto, pero con funcionalidad adicional. Útil para registro, validación, caché.

    // Ejemplo de uso del decorador
    class Component {
      operation() {
        return "Componente Básico";
      }
    }
    
    class Decorator {
      constructor(component) {
        this.component = component;
      }
    
      operation() {
        return this.component.operation();
      }
    }
    
    class ConcreteDecoratorA extends Decorator {
      operation() {
        return `DecoradorConcretoA(${super.operation()})`;
      }
    }
    
    class ConcreteDecoratorB extends Decorator {
      operation() {
        return `DecoradorConcretoB(${super.operation()})`;
      }
    }
    
    const simple = new Component();
    console.log(simple.operation());
    
    const decoratorA = new ConcreteDecoratorA(simple);
    console.log(decoratorA.operation());
    
    const decoratorB = new ConcreteDecoratorB(decoratorA);
    console.log(decoratorB.operation());
    
  6. Módulo (Module): Aunque en Node.js el sistema de módulos está incorporado (CommonJS, ES Modules), el patrón Module pattern en JavaScript (antes de la aparición de módulos nativos) se usaba para crear miembros privados y una interfaz pública. Es la base para construir código encapsulado.

Estos patrones me ayudaron a crear un código más estructurado, escalable y mantenible.