Saltar al contenidoJMRG
Todas las entradas

Patrón Abstract Factory: Familias de Objetos Relacionados

design-patternscreationalabstract-factory

Intención

Abstract Factory proporciona una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas. Garantiza que los productos creados sean compatibles entre sí.

El Problema

Imagina un sistema de UI que debe soportar múltiples temas. Cada tema tiene botones, inputs y cards con estilos consistentes:

// Sin patrón: lógica de tema dispersa
function createButton(theme: string) {
  if (theme === 'light') return new LightButton();
  if (theme === 'dark') return new DarkButton();
}

function createInput(theme: string) {
  if (theme === 'light') return new LightInput();
  if (theme === 'dark') return new DarkInput();
}

// ¿Qué pasa si alguien mezcla un LightButton con DarkInput?

La Solución: Abstract Factory

classDiagram
    class UIFactory {
        <<interface>>
        +createButton(): Button
        +createInput(): Input
        +createCard(): Card
    }

    class LightThemeFactory {
        +createButton(): LightButton
        +createInput(): LightInput
        +createCard(): LightCard
    }

    class DarkThemeFactory {
        +createButton(): DarkButton
        +createInput(): DarkInput
        +createCard(): DarkCard
    }

    UIFactory <|.. LightThemeFactory
    UIFactory <|.. DarkThemeFactory

Implementación TypeScript

// Productos abstractos
interface Button {
  render(): string;
  onClick(handler: () => void): void;
}

interface Input {
  render(): string;
  getValue(): string;
}

// Fábrica abstracta
interface UIFactory {
  createButton(label: string): Button;
  createInput(placeholder: string): Input;
}

// Fábrica concreta: Light Theme
class LightThemeFactory implements UIFactory {
  createButton(label: string): Button {
    return {
      render: () => `<button class="bg-white text-gray-900 border">${label}</button>`,
      onClick: (handler) => handler(),
    };
  }

  createInput(placeholder: string): Input {
    return {
      render: () => `<input class="bg-white border-gray-300" placeholder="${placeholder}">`,
      getValue: () => '',
    };
  }
}

// Fábrica concreta: Dark Theme
class DarkThemeFactory implements UIFactory {
  createButton(label: string): Button {
    return {
      render: () => `<button class="bg-gray-800 text-white">${label}</button>`,
      onClick: (handler) => handler(),
    };
  }

  createInput(placeholder: string): Input {
    return {
      render: () => `<input class="bg-gray-900 text-white" placeholder="${placeholder}">`,
      getValue: () => '',
    };
  }
}

// Uso con inyección de dependencias
class LoginForm {
  constructor(private factory: UIFactory) {}

  render(): string {
    const emailInput = this.factory.createInput('Email');
    const passwordInput = this.factory.createInput('Password');
    const submitButton = this.factory.createButton('Login');

    return `
      <form>
        ${emailInput.render()}
        ${passwordInput.render()}
        ${submitButton.render()}
      </form>
    `;
  }
}

// El cliente decide qué fábrica usar
const theme = getUserPreference();
const factory = theme === 'dark' ? new DarkThemeFactory() : new LightThemeFactory();
const loginForm = new LoginForm(factory);

Cuándo Usar

Situación Recomendación
Múltiples temas/skins ✅ Recomendado
Soporte multiplataforma ✅ Recomendado
Familias de productos relacionados ✅ Recomendado
Producto único sin variantes ❌ Usar Factory Method

Conclusión

Abstract Factory garantiza consistencia al crear familias de objetos relacionados, evitando combinaciones incompatibles.