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.

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.

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.

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.
