Saltar al contenidoJMRG
Todas las entradas

Patrón Factory Method: Delegando la Creación de Objetos

design-patternscreationalfactory-method

¿Qué es el Patrón Factory Method?

El patrón Factory Method define una interfaz para crear objetos, pero permite a las subclases decidir qué clase instanciar. Es ideal cuando no conoces de antemano los tipos exactos de objetos que necesitas crear o cuando quieres delegar la lógica de creación a subclases especializadas.

classDiagram
    class Creator {
        <<abstract>>
        +factoryMethod(): Product
        +operation()
    }

    class ConcreteCreatorA {
        +factoryMethod(): ProductA
    }

    class ConcreteCreatorB {
        +factoryMethod(): ProductB
    }

    class Product {
        <<interface>>
        +use()
    }

    class ProductA {
        +use()
    }

    class ProductB {
        +use()
    }

    Creator <|-- ConcreteCreatorA
    Creator <|-- ConcreteCreatorB
    Product <|.. ProductA
    Product <|.. ProductB
    Creator ..> Product

Cuándo Usar Factory Method

Usar Factory Method:

  • Cuando no conoces de antemano los tipos exactos de objetos
  • Para proporcionar una forma de extender los componentes internos
  • Para reutilizar objetos existentes en lugar de crear nuevos
  • Integración con diferentes proveedores (pagos, notificaciones, etc.)

Evitar Factory Method:

  • Si solo tienes un tipo de producto (sobreingeniería)
  • Cuando la creación es trivial y no cambiará

Implementación Paso a Paso

1. Interfaz del Producto

Define el contrato común que todos los productos deben implementar:

interface NotificationResult {
  success: boolean;
  messageId: string;
  timestamp: Date;
}

interface Notification {
  readonly type: string;
  send(recipient: string, message: string): Promise<NotificationResult>;
  validate(recipient: string): boolean;
}

2. Productos Concretos

Implementaciones específicas de la interfaz:

class EmailNotification implements Notification {
  readonly type = 'email';

  validate(recipient: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(recipient);
  }

  async send(recipient: string, message: string): Promise<NotificationResult> {
    console.log(`Sending email to ${recipient}: ${message}`);
    return {
      success: true,
      messageId: `email_${Date.now()}`,
      timestamp: new Date(),
    };
  }
}

class SMSNotification implements Notification {
  readonly type = 'sms';

  validate(recipient: string): boolean {
    const phoneRegex = /^\+?[1-9]\d{9,14}$/;
    return phoneRegex.test(recipient);
  }

  async send(recipient: string, message: string): Promise<NotificationResult> {
    console.log(`Sending SMS to ${recipient}: ${message}`);
    return {
      success: true,
      messageId: `sms_${Date.now()}`,
      timestamp: new Date(),
    };
  }
}

3. Creador Abstracto

Define el factory method abstracto y puede incluir lógica común:

abstract class NotificationCreator {
  /** Factory Method - las subclases deciden qué crear */
  protected abstract createNotification(): Notification;

  /** Template Method que usa el factory method */
  public async notify(
    recipient: string,
    message: string
  ): Promise<NotificationResult> {
    const notification = this.createNotification();

    if (!notification.validate(recipient)) {
      throw new Error(`Destinatario inválido para ${notification.type}`);
    }

    return notification.send(recipient, message);
  }
}

4. Creadores Concretos

Implementan el factory method para crear productos específicos:

class EmailNotificationCreator extends NotificationCreator {
  protected createNotification(): Notification {
    return new EmailNotification();
  }
}

class SMSNotificationCreator extends NotificationCreator {
  protected createNotification(): Notification {
    return new SMSNotification();
  }
}

Ejemplo de Uso

// Usando creadores específicos
const emailCreator = new EmailNotificationCreator();
await emailCreator.notify('[email protected]', '¡Bienvenido!');

const smsCreator = new SMSNotificationCreator();
await smsCreator.notify('+34612345678', 'Tu código es: 123456');

Variante: Factory con Registro Dinámico

Permite registrar nuevos tipos en tiempo de ejecución:

type NotificationType = 'email' | 'sms' | 'push' | 'slack';

class NotificationFactory {
  private static creators = new Map<NotificationType, () => Notification>();

  static register(type: NotificationType, creator: () => Notification): void {
    this.creators.set(type, creator);
  }

  static create(type: NotificationType): Notification {
    const creator = this.creators.get(type);
    if (!creator) {
      throw new Error(`Tipo de notificación no registrado: ${type}`);
    }
    return creator();
  }
}

// Registro de factories
NotificationFactory.register('email', () => new EmailNotification());
NotificationFactory.register('sms', () => new SMSNotification());

// Uso
const notification = NotificationFactory.create('email');
await notification.send('[email protected]', 'Hello!');

Resumen

Aspecto Descripción
Propósito Delegar la creación de objetos a subclases
Componentes Producto (interfaz), Productos concretos, Creador abstracto, Creadores concretos
Beneficios Desacoplamiento, extensibilidad, principio Open/Closed
Variantes Factory con registro dinámico, Parametrized Factory

Conclusión

El patrón Factory Method es una herramienta esencial para manejar la creación de objetos de forma flexible y extensible. Es la base de muchos frameworks y librerías modernas.

En el próximo artículo exploraremos el Patrón Decorator, que permite añadir comportamiento a objetos dinámicamente.


Basado en "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four).