patron-abstract-factory

Patrón Abstract Factory: familias de objetos

  • 5 min

El Abstract Factory es una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas. El cliente recibe una fábrica y trabaja con los productos abstractos que esta ofrece.

En el artículo anterior vimos cómo el Factory Method nos salvaba de usar new para crear un solo tipo de producto (como un Transporte).

Pero, ¿qué pasa cuando nuestros objetos no vienen solos? ¿Qué ocurre cuando necesitamos crear familias de objetos que deben funcionar juntas y ser coherentes entre sí?

Imagina que estás desarrollando una interfaz gráfica. Si el usuario elige el tema oscuro, todos los elementos deben mantener ese estilo. No quieres mezclar un botón de Windows 95 con una ventana de macOS moderno.

El patrón permite mantener coherentes los productos que creamos juntos.

El Problema: Familias de Productos

Supongamos que estamos haciendo un simulador de batallas medievales y futuristas.

Tenemos dos familias de productos:

  1. Familia Medieval: Orco, Castillo, Espada.
  2. Familia Futurista: Alien, BaseEspacial, Laser.

Si usamos un simple Factory Method, podríamos equivocarnos y acabar creando un Orco armado con un Laser dentro de una BaseEspacial. El compilador no se quejaría, pero la lógica del juego estaría rota.

Necesitamos una forma de decirle al sistema: “Dame todos los objetos necesarios para el escenario Medieval”, y que el sistema nos garantice que todos los objetos que nos devuelva serán de esa época.

La Solución: Una Fábrica de Fábricas

El patrón Abstract Factory define una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.

Básicamente, creamos una interfaz (la “Fábrica Abstracta”) que tiene una lista de métodos de creación para cada tipo de producto de la familia (CrearGuerrero, CrearArma, CrearEdificio).

Luego, creamos fábricas concretas (FabricaMedieval, FabricaFuturista) que implementan esa interfaz y devuelven los productos correspondientes.

Implementación en C# (Ejemplo UI)

Vamos a implementar el ejemplo clásico de una librería de UI que soporta dos sistemas operativos: Windows y Mac. Queremos botones y checkboxes que coincidan con el SO.

Los Productos Abstractos y Concretos

Primero definimos las interfaces de nuestros componentes y sus implementaciones.

// --- PRODUCTO A: BOTÓN ---
public interface IButton
{
    void Paint();
}

public class WindowsButton : IButton
{
    public void Paint() => Console.WriteLine("Renderizando un botón estilo WINDOWS");
}

public class MacButton : IButton
{
    public void Paint() => Console.WriteLine("Renderizando un botón estilo MAC");
}

// --- PRODUCTO B: CHECKBOX ---
public interface ICheckbox
{
    void Paint();
}

public class WindowsCheckbox : ICheckbox
{
    public void Paint() => Console.WriteLine("Renderizando un checkbox estilo WINDOWS");
}

public class MacCheckbox : ICheckbox
{
    public void Paint() => Console.WriteLine("Renderizando un checkbox estilo MAC");
}
Copied!

La Abstract Factory

Aquí definimos el contrato: cualquier fábrica de UI debe saber crear botones y checkboxes.

public interface IGUIFactory
{
    IButton CrearBoton();
    ICheckbox CrearCheckbox();
}
Copied!

Las Fábricas Concretas

Aquí garantizamos la coherencia. La WinFactory solo devuelve objetos de Windows. Es imposible mezclar estilos aquí.

public class WinFactory : IGUIFactory
{
    public IButton CrearBoton()
    {
        return new WindowsButton();
    }

    public ICheckbox CrearCheckbox()
    {
        return new WindowsCheckbox();
    }
}

public class MacFactory : IGUIFactory
{
    public IButton CrearBoton()
    {
        return new MacButton();
    }

    public ICheckbox CrearCheckbox()
    {
        return new MacCheckbox();
    }
}
Copied!

El Cliente (La Aplicación)

El cliente no sabe si está en Windows o Mac. Solo sabe que tiene una fábrica y le pide cosas.

public class Application
{
    private readonly IButton _button;
    private readonly ICheckbox _checkbox;

    // Inyección de dependencias: Recibimos la fábrica abstracta
    public Application(IGUIFactory factory)
    {
        // Creamos la familia de productos
        _button = factory.CrearBoton();
        _checkbox = factory.CrearCheckbox();
    }

    public void Paint()
    {
        _button.Paint();
        _checkbox.Paint();
    }
}
Copied!

Ejecución

class Program
{
    static void Main(string[] args)
    {
        // Simulamos configuración de entorno
        IGUIFactory factory;
        string os = "Windows"; // Esto vendría de config

        if (os == "Windows")
            factory = new WinFactory();
        else
            factory = new MacFactory();

        // La aplicación funciona igual independientemente de la fábrica
        var app = new Application(factory);
        app.Paint();
    }
}
Copied!

Salida:

Renderizando un botón estilo WINDOWS
Renderizando un checkbox estilo WINDOWS
Copied!

Si cambiamos os = "Mac", automáticamente toda la aplicación cambia de aspecto, y tenemos la garantía de que no habrá un checkbox de Windows perdido por ahí.

Ventajas y Desventajas

VentajasDesventajas
Consistencia: Garantiza que los productos que usas juntos son compatibles.Complejidad: El código se llena de interfaces y clases. Si solo tienes dos clases, es matar moscas a cañonazos.
Desacoplamiento: El cliente no toca clases concretas.Rigidez: Añadir un nuevo tipo de producto (ej: CrearSlider) es doloroso. Tienes que cambiar la interfaz IGUIFactory y todas las fábricas concretas.

Diferencia con Factory Method

Es fácil confundirse, así que aquí tienes una idea práctica:

  • Factory Method: Se usa para crear un único producto. Se basa en la herencia (delega en subclases).
  • Abstract Factory: Se usa para crear familias de productos. Se basa en la composición (el cliente tiene una referencia a la fábrica).

De hecho, a menudo una Abstract Factory se implementa usando Factory Methods dentro de ella.