7. Factory Method (Método de Fábrica) - Patrón Creacional

El patrón Factory Method delega la creación de objetos a clases hijas mediante un método factoría. En lugar de instanciar productos concretos directamente, el cliente interactúa con una interfaz o clase abstracta que difiere la elección de la implementación concreta a subclasses especializadas. Este enfoque fomenta el polimorfismo y desacopla la lógica de negocio de los detalles de construcción.

El patrón resulta esencial cuando se prevé la aparición de nuevas variantes de un producto y se busca mantener cerrado el código existente a modificaciones directas. Así, la extensión del sistema se logra creando nuevas factorías y productos concretos sin alterar las clases ya probadas.

7.1 Problema que resuelve

La instanciación directa con new ligan fuertemente al código cliente con clases concretas. Cuando surgen nuevas implementaciones, cada referencia debe actualizarse, lo que genera mantenimiento costoso y posibles regresiones. Además, la duplicación de lógica de creación (validaciones, configuraciones, dependencias) se vuelve recurrente en distintos puntos del programa.

Factory Method establece un punto centralizado y flexible para producir objetos de una jerarquía determinada. El cliente opera con la abstracción y queda protegido frente a cambios en las clases concretas o en su ciclo de vida.

7.2 Intención y motivación

Su intención es definir una interfaz para crear un objeto, dejando que las subclasses decidan qué clase instanciar. Cada factoría concreta encapsula la elección del producto y puede aplicar configuraciones específicas sin que el código consumidor se entere.

Es habitual en frameworks donde el usuario extiende clases base provistas por la librería. Las factorías permiten insertar comportamientos personalizados (por ejemplo, elegir un controlador HTTP, un parser o un componente visual) sin modificar el framework. También resulta útil cuando se desean distintas variantes del mismo producto basadas en contexto, como tipo de persistencia, canal de entrega o estrategia de autenticación.

7.3 Estructura y participantes

La estructura está compuesta por cuatro elementos principales:

  • Producto: interfaz o clase abstracta que define las operaciones que usará el cliente.
  • Productos concretos: implementaciones específicas del producto.
  • Creador: clase abstracta que declara el factory method y puede incluir comportamiento por defecto que delega en el producto retornado.
  • Creadores concretos: subclasses que sobrescriben el método factoría para devolver productos concretos.

El creador suele contener métodos de negocio comunes que operan con el producto. De esta forma, las nuevas variantes se incorporan agregando un creador concreto y su producto asociado, respetando el principio de abierto/cerrado.

7.4 Variantes y relación con otros patrones

El patrón a veces se confunde con una simple factory. En la versión simple, un método estático decide qué producto instanciar, pero no hay herencia ni sobrescritura: el código debe modificarse cada vez que surge una nueva opción. Factory Method delega la elección a subclasses, evitando modificar el método original.

Cuando se necesita crear familias completas de productos relacionados, Factory Method resulta insuficiente y se recurre al patrón Abstract Factory, que agrupa varios métodos factoría. También aparece como paso de construcción dentro de Builder, donde cada etapa puede usar su propio método de fábrica.

7.5 Implementación básica en Java

A continuación se muestra una implementación clásica en Java, donde un creador abstracto delega en subclasses la instanciación de un botón para distintas plataformas.

public interface Boton {
    void dibujar();
    void click();
}

public class BotonWindows implements Boton {
    @Override
    public void dibujar() {
        System.out.println("Botón con estilo Windows");
    }

    @Override
    public void click() {
        System.out.println("Acción Windows ejecutada");
    }
}

public class BotonWeb implements Boton {
    @Override
    public void dibujar() {
        System.out.println("Botón con estilo Web");
    }

    @Override
    public void click() {
        System.out.println("Acción Web ejecutada");
    }
}

public abstract class Dialogo {
    public void renderizar() {
        Boton boton = crearBoton();
        boton.dibujar();
        boton.click();
    }

    protected abstract Boton crearBoton();
}

public class DialogoWindows extends Dialogo {
    @Override
    protected Boton crearBoton() {
        return new BotonWindows();
    }
}

public class DialogoWeb extends Dialogo {
    @Override
    protected Boton crearBoton() {
        return new BotonWeb();
    }
}

class Aplicacion {
    public static void main(String[] args) {
        Dialogo dialogo = new DialogoWindows();
        dialogo.renderizar();

        dialogo = new DialogoWeb();
        dialogo.renderizar();
    }
}

El método renderizar es independiente de la plataforma concreta: solo invoca operaciones del producto. Agregar un nuevo estilo implica crear un botón y un diálogo concretos sin tocar el código existente.

Factory Method

7.6 Ejemplo completo en Java: generación de reportes

Consideremos un sistema que genera reportes con diferentes formatos (PDF, CSV y HTML). Cada formato requiere bibliotecas y estrategias distintas, por lo que la instanciación directa generaría condicionales repartidos. Aplicaremos Factory Method para centralizar la elección.

public interface Reporte {
    void crearContenido();
    void exportar(String destino);
}

public class ReportePdf implements Reporte {
    @Override
    public void crearContenido() {
        System.out.println("Renderizando contenido en PDF");
    }

    @Override
    public void exportar(String destino) {
        System.out.println("Exportando PDF a " + destino);
    }
}

public class ReporteCsv implements Reporte {
    @Override
    public void crearContenido() {
        System.out.println("Generando celdas separado por comas");
    }

    @Override
    public void exportar(String destino) {
        System.out.println("Guardando CSV en " + destino);
    }
}

public class ReporteHtml implements Reporte {
    @Override
    public void crearContenido() {
        System.out.println("Construyendo estructura HTML");
    }

    @Override
    public void exportar(String destino) {
        System.out.println("Publicando HTML en " + destino);
    }
}

public abstract class GeneradorReportes {
    public void generar(String destino) {
        Reporte reporte = crearReporte();
        prepararRecursos();
        reporte.crearContenido();
        reporte.exportar(destino);
        liberarRecursos();
    }

    protected abstract Reporte crearReporte();

    protected void prepararRecursos() {
        System.out.println("Inicializando conexiones y plantillas");
    }

    protected void liberarRecursos() {
        System.out.println("Liberando conexiones");
    }
}

public class GeneradorPdf extends GeneradorReportes {
    @Override
    protected Reporte crearReporte() {
        return new ReportePdf();
    }
}

public class GeneradorCsv extends GeneradorReportes {
    @Override
    protected Reporte crearReporte() {
        return new ReporteCsv();
    }
}

public class GeneradorHtml extends GeneradorReportes {
    @Override
    protected Reporte crearReporte() {
        return new ReporteHtml();
    }
}

class Aplicacion {
    public static void main(String[] args) {
        GeneradorReportes generador = new GeneradorPdf();
        generador.generar("/tmp/informe.pdf");

        generador = new GeneradorCsv();
        generador.generar("/tmp/informe.csv");
    }
}

El cliente solo elige el generador adecuado, sin conocer los detalles de cada reporte:

La extensión a nuevos formatos se limita a crear un reporte y un generador concretos. No hay condicionales esparcidos ni cambios en el código probador.

7.7 Buenas prácticas y variaciones

Entre las recomendaciones más frecuentes se encuentran:

  • Evitar que el método factoría devuelva null; es preferible lanzar una excepción o entregar un objeto nulo seguro.
  • Combinar Factory Method con inyección de dependencias cuando las clases concretas requieren colaboraciones complejas.
  • Utilizar nombres descriptivos para las factorías concretas (por ejemplo, GeneradorPdf) que expresan la variante creada.
  • Aplicar el patrón en capas de infraestructura donde cambian proveedores (motores de base de datos, drivers de mensajería, motores de plantilla).

Una variación común consiste en parametrizar el creador con datos de configuración. La factoría concreta puede leer archivos externos o servicios para decidir qué producto devolver, manteniendo el aislamiento respecto al cliente.

7.8 Errores habituales y cómo evitarlos

El abuso del patrón puede conducir a jerarquías innecesarias. Si solo existe una implementación del producto y no se espera introducir otras, un Factory Method agrega complejidad sin beneficio. También es frecuente caer en factorías que contienen grandes estructuras condicionales: en ese caso conviene dividirlas en subclasses independientes.

Otra fuente de problemas es mezclar responsabilidades. El creador no debería encargarse de la lógica de negocio ajena a la creación del producto. Mantenerlo enfocado facilita probar cada variante por separado y reutilizarlo en distintas capas.

7.9 Cuándo elegir Factory Method

El patrón destaca en escenarios donde se prevé la evolución del producto y se desea aislar al cliente de cambios concretos. Es ideal para bibliotecas y frameworks que exponen puntos de extensión, para sistemas con módulos intercambiables y para integraciones que dependen de proveedores. Cuando la configuración debe variar en tiempo de ejecución, el factory method ofrece un gancho natural para decidir qué objeto crear.