Software

Factory Pattern: qué es, variantes y código explicado

Simple Factory, Factory Method y Abstract Factory sin confusión, con código en Java, Python y TypeScript, los anti-patterns que salen caros y las preguntas que separan a quien lo ha usado de quien lo ha leído.

·15 min·Pedro Cailá · Kulturo
Factory Pattern: qué es, variantes y código explicado

La recomendación más repetida sobre el factory pattern es también una de las menos útiles: “si quieres desacoplar la creación, crea una factoría”. No siempre. En muchos servicios actuales, un constructor claro, una función factoría o un contenedor de inyección de dependencias resuelven el problema con menos código y menos indirección.

La pregunta profesional no es si conoces el patrón, sino si puedes justificar su coste. El Factory Method quedó formalizado en el catálogo GoF de 1994 como un patrón creacional que permite definir una interfaz de creación y dejar que las subclases decidan qué clase instanciar, una formulación que sigue siendo la referencia histórica del concepto (descripción histórica del patrón Factory Method). La teoría importa, pero en un equipo real importan más las consecuencias: testabilidad, trazabilidad, facilidad para añadir implementaciones y esfuerzo de mantenimiento.

Por qué el factory pattern sigue generando debate en 2026

Una clase que solo contiene return new PaymentService() no es automáticamente una buena abstracción. Puede ser una capa vacía entre el cliente y el constructor, creada porque el equipo ha confundido usar un patrón con mejorar el diseño.

En aplicaciones backend, herramientas como Spring, NestJS o Dagger ya resuelven buena parte de la construcción, el registro y la inyección de dependencias. Si el contenedor conoce una única implementación estable y el objeto no requiere decisiones especiales, añadir una factoría puede ocultar la configuración sin aportar una capacidad nueva. El cliente deja de ver new, pero el sistema no ha ganado flexibilidad real.

El problema de las factorías triviales

El patrón sí tiene sentido cuando la elección depende de información que aparece durante la ejecución. Un módulo puede seleccionar un gateway según el canal de pago, un adaptador HTTP según el entorno o un serializador según el formato solicitado. También resulta útil cuando crear el objeto implica validar configuración, combinar dependencias o elegir entre varias implementaciones compatibles.

Criterio de ingeniería: si eliminar la factoría deja el código más directo y no aumenta el acoplamiento, probablemente la factoría sobraba.

La diferencia entre una factoría genérica y Factory Method también merece precisión. Una factory puede ser cualquier función, método o clase que produzca algo, mientras que Factory Method es una variante concreta en la que una interfaz de creación permite que las subclases decidan qué producto instanciar (comparación entre Factory y Factory Method). Esa distinción evita llamar “Factory Method” a cualquier método create().

En equipos pequeños, el riesgo es práctico: una factoría central puede crecer hasta convertirse en una lista interminable de condicionales, mientras que una factoría estática global puede dificultar los tests. Mi postura es clara: usa una factory cuando concentre una decisión variable o una construcción compleja; no la uses para esconder un constructor sencillo.

Qué resuelve realmente el factory pattern

Empieza por el cliente, no por el patrón. Un servicio de pedidos necesita enviar una notificación, pero no debería conocer si la entrega se hará por correo electrónico, SMS o push. Su responsabilidad es trabajar con un contrato, por ejemplo Notification, y delegar la elección concreta en otro punto.

La analogía de un restaurante ayuda. El camarero solicita un plato del menú, pero no necesita saber qué cocinero lo prepara ni qué pasos internos sigue la cocina. El cliente pide un objeto con una capacidad conocida, y la factoría decide qué implementación concreta satisface esa petición.

Diagrama explicativo sobre el patrón de diseño Factory, mostrando la interacción entre cliente, interfaz y objetos.

El desacoplo que sí importa

En materiales docentes españoles, el patrón se presenta como una forma de crear objetos sin especificar su clase exacta, seleccionando e instanciando una clase apropiada dentro de una jerarquía (material docente de la Universitat de Girona sobre Factory Method). La idea práctica es sencilla: el consumidor depende del contrato, no del tipo concreto.

Ese diseño resulta útil en escenarios como estos:

  • Gateways de pago: el caso de uso trabaja con PaymentGateway, mientras una decisión de configuración o de negocio selecciona una implementación.
  • Clientes HTTP: el sistema puede crear un adaptador para un proveedor, una región o un entorno sin propagar esa decisión por toda la aplicación.
  • Serializadores: el código consumidor solicita un serializador compatible con JSON, XML u otro formato, sin construir directamente cada clase.
  • Adaptadores de API: una integración externa puede cambiar sin obligar al dominio a conocer los detalles de cada SDK.

La ganancia no consiste en ocultar cada aparición de new. Consiste en centralizar el porqué de la creación. Si elegir una clase requiere condicionales, validación, configuración o una política de selección, una factoría evita que esa lógica se reparta entre controladores y servicios.

La documentación técnica en español de Microsoft y recursos académicos han difundido este enfoque como una manera de separar la creación del uso del objeto (explicación práctica del patrón Factory en español). Esa separación es valiosa solo si el cliente realmente queda aislado de las clases concretas.

Simple Factory, Factory Method y Abstract Factory sin confusión

Las tres variantes responden a problemas distintos. No conviene elegirlas por prestigio ni por la cantidad de clases que permiten crear.

Simple Factory

Una Simple Factory suele ser una función o una clase con un método que recibe una clave y devuelve una implementación. Si una aplicación envía notificaciones por un canal, puede bastar con:

NotificationFactory.create("email")

Es una solución directa cuando la decisión está concentrada y no necesitas una jerarquía de creadoras. Su límite aparece cuando la clase acumula reglas de configuración, permisos, proveedores y excepciones. En ese punto, la factoría empieza a comportarse como una God Class.

Factory Method

Factory Method encaja cuando existe una clase base con un flujo común, pero distintas subclases deben crear productos diferentes. Un gestor de documentos puede definir el proceso general de abrir, validar y procesar un documento, mientras PdfDocumentManager y XmlDocumentManager implementan su propia creación.

La variante aporta valor si esas subclases son reales y evolucionan de forma independiente. Si solo existe una subclase y no hay una previsión razonable de variación, la jerarquía añade ceremonia. En equipos Java, este enfoque separa el conocimiento de qué crear del conocimiento de cómo usarlo y puede facilitar dobles de test (Factory Method en Java).

Abstract Factory

Abstract Factory es la opción adecuada cuando necesitas familias de objetos relacionados que deben ser compatibles entre sí. Un kit de interfaz multiplataforma puede crear botones, menús y diálogos con un tema coherente, por ejemplo claro u oscuro, sin mezclar componentes de familias distintas.

No la usaría para un único producto variable. En ese caso, una Simple Factory o una inyección directa suele resultar más legible.

Criterio Simple Factory Factory Method Abstract Factory
Problema principal Elegir una implementación Delegar la creación en subclases Crear familias compatibles
Estructura Función o clase central Clase base y creadoras concretas Interfaz de fábrica y productos relacionados
Úsala cuando La decisión está concentrada La jerarquía evoluciona Hay varias familias de productos
Evítala cuando La función solo envuelve un constructor No existen subclases reales Solo cambia un producto

La regla más útil es esta: una dimensión de variación suele pedir una factory; varias dimensiones relacionadas pueden justificar Abstract Factory.

Ejemplos de código en Java, Python y TypeScript

El mismo caso puede expresarse de forma muy distinta según el lenguaje. Para notificaciones, la familia de productos será EmailNotification, SmsNotification y PushNotification.

Java con una jerarquía explícita

Java hace visible cada pieza del patrón:

interface Notification {
    void send(String message);
}

class EmailNotification implements Notification {
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

class SmsNotification implements Notification {
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

class PushNotification implements Notification {
    public void send(String message) {
        System.out.println("Push: " + message);
    }
}

abstract class NotificationCreator {
    abstract Notification create();
}

class NotificationFactory extends NotificationCreator {
    private final Map<String, Supplier<Notification>> types = Map.of(
        "email", EmailNotification::new,
        "sms", SmsNotification::new,
        "push", PushNotification::new
    );

    private final String channel;

    NotificationFactory(String channel) {
        this.channel = channel;
    }

    Notification create() {
        Supplier<Notification> creator = types.get(channel);
        if (creator == null) throw new IllegalArgumentException("Canal no válido");
        return creator.get();
    }
}

La ventaja es la claridad contractual. El coste es la verbosidad: interfaces, clases y tipos explícitos hacen que el diseño sea fácil de inspeccionar, pero más pesado de modificar.

Python con una función ligera

Python suele resolver el mismo problema con menos ceremonia:

from dataclasses import dataclass

@dataclass
class EmailNotification:
    def send(self, message: str) -> None:
        print(f"Email: {message}")

@dataclass
class SmsNotification:
    def send(self, message: str) -> None:
        print(f"SMS: {message}")

@dataclass
class PushNotification:
    def send(self, message: str) -> None:
        print(f"Push: {message}")

types = {
    "email": EmailNotification,
    "sms": SmsNotification,
    "push": PushNotification,
}

def create_notification(channel: str):
    try:
        return types[channel]()
    except KeyError:
        raise ValueError("Canal no válido")

Aquí una función factory es suficiente. El equipo no necesita inventar una clase para almacenar una decisión que ya puede expresar un diccionario de clases. En aplicaciones Flask, el mismo principio se usa para crear instancias configuradas mediante una función create_app, separando configuración y construcción de la aplicación (organización de una App Factory en Flask).

TypeScript con una unión discriminada

TypeScript permite mantener una factory pequeña y conservar comprobaciones del compilador:

type Channel = "email" | "sms" | "push";

type Notification =
  | { kind: "email"; send: (message: string) => void }
  | { kind: "sms"; send: (message: string) => void }
  | { kind: "push"; send: (message: string) => void };

function createNotification(channel: Channel): Notification {
  return {
    kind: channel,
    send: (message) => console.log(`${channel}: ${message}`)
  };
}

function deliver(notification: Notification, message: string) {
  switch (notification.kind) {
    case "email":
    case "sms":
    case "push":
      notification.send(message);
      break;
  }
}

La ergonomía depende del objetivo: Java favorece una estructura explícita, Python una composición compacta y TypeScript un equilibrio entre flexibilidad y control estático. Para ampliar la comparación entre ambos estilos, puede consultarse este análisis sobre Python frente a Java.

Lenguaje Forma idiomática Tipado Líneas aprox. Punto fuerte
Java Jerarquía y creadora concreta Estático explícito Varias Contratos visibles
Python Función y registro de clases Dinámico con anotaciones Pocas Rapidez y simplicidad
TypeScript Función y unión discriminada Estático Pocas Control del compilador

Factory pattern, inyección de dependencias y testabilidad

Una factoría no sustituye a un contenedor IoC. Cumplen funciones relacionadas, pero distintas. La factoría decide qué objeto crear bajo una condición concreta; el contenedor gestiona cómo registrar, inyectar y mantener las dependencias.

Diagrama comparativo que muestra la diferencia entre una factoría y un contenedor de inversión de control.

Spring, NestJS y FastAPI pueden construir servicios al iniciar la aplicación. La factory aparece cuando la elección depende de datos dinámicos, como un canal, un plan, un país o una capacidad negociada con un proveedor. En ese caso, el contenedor ofrece las piezas y la factory decide cuál usar.

Un borde claro para los tests

Supón que CheckoutService recibe PaymentGatewayFactory. En una prueba unitaria de Java, Mockito puede reemplazar esa dependencia:

when(factory.create("card")).thenReturn(fakeGateway);
service.pay(order);
verify(fakeGateway).charge(order);

En Python, unittest.mock permite parchear o sustituir la función factory sin inicializar toda la aplicación. En TypeScript, Vitest puede usar vi.fn() para devolver un adaptador controlado. El test comprueba la decisión del caso de uso sin levantar el grafo completo del contenedor.

Esa frontera mejora el diseño cuando el consumidor depende de una interfaz y la factory se inyecta como colaboración. La documentación sobre testing automatizado ayuda a situar esta práctica dentro de una estrategia más amplia, pero el principio es concreto: prueba la lógica de selección por separado de la infraestructura.

Este vídeo puede servir como apoyo visual para explicar la relación entre factorías, dependencias y composición:

La factory correcta suele vivir en el borde entre configuración y lógica de negocio. Si aparece dentro de cada servicio como un localizador global, el diseño empieza a perder transparencia.

Anti-patterns y cuándo una factory solo desplaza la complejidad

Una factory puede reducir acoplamiento, pero también puede esconderlo. El caso más evidente es una clase con un método que solo hace return new User(). El cliente sigue dependiendo conceptualmente de User, y la nueva capa solo obliga a saltar a otro archivo.

Infografía sobre los anti-patrones y casos de uso adecuados para el patrón de diseño Factory Pattern.

Señales de que el diseño se ha torcido

  • Builder disfrazado: la factory recibe una lista extensa de parámetros opcionales, aplica reglas de construcción y termina simulando un Builder sin reconocerlo.
  • Factory global estática: cualquier parte del sistema llama a GlobalFactory.create(), lo que oculta dependencias y complica el aislamiento en tests.
  • Condicionales sin límite: cada nuevo producto añade otro if, otra dependencia y otra excepción a la misma clase.
  • Capa innecesaria: el consumidor no necesitaba variar la implementación y el contenedor ya podía inyectar el objeto directamente.

En frontend, React y Vue suelen resolver la composición mediante componentes, props y renderizado condicional. Una Abstract Factory rara vez mejora una interfaz cuando la variación puede expresarse de forma declarativa. También puede dificultar el tree-shaking y el debugging si importa módulos dinámicamente sin una estrategia clara de observabilidad.

La prueba decisiva es la del cliente: ¿el consumidor conoce todavía la clase concreta, sus parámetros internos o el proveedor específico? Si la respuesta es sí, la factory no ha completado el desacoplo.

Cuando la decisión cambia el algoritmo, no la construcción, usa Strategy. Cuando el contenedor conoce todas las dependencias, usa DI directa. Cuando las implementaciones se registran por extensión, considera un registro explícito o decoradores. La abstracción correcta no es la más sofisticada, sino la que deja menos decisiones ocultas.

Preguntas de entrevista para evaluar a un candidato

Una entrevista de 45 minutos no debería convertirse en un examen de nombres GoF. Las mejores preguntas presentan un problema concreto y observan si el candidato razona sobre acoplamiento, cambio y pruebas.

Pregunta Objetivo evaluado Nivel Respuesta esperada
¿Cuándo llamarías factory a una clase? Precisión conceptual Mid Cuando centraliza la creación o selección de productos, no por tener un método create()
¿Cómo resolverías canales de notificación sin nombrar el patrón? Diseño pragmático Mid Contrato común y selección aislada, con función, registro o clase según la complejidad
¿Qué coste introduce Abstract Factory en los tests? Pensamiento sistémico Senior Más interfaces y dobles, aunque puede garantizar familias compatibles
¿Factory o DI para varias pasarelas de pago? Criterio arquitectónico Senior DI para registrar dependencias, factory si la elección depende de datos de ejecución
¿Qué harías con una factory de muchos parámetros? Detección de diseño defectuoso Mid/Senior Separar configuración, valorar Builder y revisar si la construcción pertenece a otro objeto

Cómo interpretar las respuestas

Una persona con nivel mid debería identificar el contrato y evitar acoplar el cliente a cada implementación. También debería reconocer que una función puede ser suficiente y que no toda factory necesita herencia.

Un perfil senior irá más lejos. Preguntará quién decide el canal, dónde viven las reglas, cómo se registran las implementaciones, qué ocurre cuando falta una clave y cómo se observa el fallo en producción. También separará la selección de la construcción y distinguirá una decisión de negocio de una simple configuración.

Las señales de alarma son claras:

  • Respuesta memorizada: repite “crear objetos sin especificar su clase” sin conectar la idea con cambios concretos.
  • Confusión entre patrón y clase estática: cree que una factory siempre debe ser una clase con un método estático.
  • Desprecio por los tests: no identifica qué dependencia puede sustituirse ni qué frontera conviene aislar.
  • Sobrediseño automático: propone Abstract Factory para una única implementación variable.

La entrevista debe valorar la justificación. Un candidato competente puede elegir una solución distinta del patrón clásico y defenderla con claridad.

Checklist de decisión antes de aplicar el patrón

Antes de crear SomethingFactory, responde estas preguntas sobre el código actual:

  • ¿Hay más de una implementación? Si no, empieza por un constructor o DI directa.
  • ¿La decisión cambia en runtime? Si depende de canal, país, plan o formato, una factory puede concentrarla.
  • ¿El cliente necesita conocer el tipo concreto? Si puede trabajar con una interfaz, el desacoplo tiene sentido.
  • ¿La creación es compleja? Si combina validación y configuración, extraerla puede mejorar la lectura.
  • ¿Necesitas lógica por entorno? Separa configuración y construcción, especialmente en aplicaciones con varias instancias.
  • ¿Hay parámetros condicionales? Si crecen, revisa Builder o divide responsabilidades antes de ampliar la factory.
  • ¿Facilita testing con mocks? Solo cuenta como beneficio si la dependencia se puede sustituir sin levantar toda la infraestructura.
  • ¿La complejidad justifica la capa extra? Si la respuesta es no, elimina la abstracción.

Documentar estas respuestas ayuda tanto a introducir como a retirar una factory. Para equipos que necesitan decidir sobre estructura, responsabilidades y evolución técnica, conviene complementar el análisis con criterios propios de un arquitecto de software.

La regla final es sencilla: si la factory concentra una variación real, mantenla; si solo cambia de sitio un constructor, elimínala.


Kulturo ayuda a startups, scaleups y equipos tecnológicos en España a contratar perfiles de backend, arquitectura, frontend, datos, cloud e inteligencia artificial con un proceso técnico alineado con sus necesidades. Si estás definiendo una arquitectura y necesitas incorporar a la persona capaz de justificar estas decisiones, visita Kulturo y habla con un especialista en recruiting tecnológico.

Tenemos el profesional que necesitas

Cuéntanos qué perfil buscas y te enviamos candidatos en menos de una semana.

Empieza a contratar