Factory Method
Un patrón creacional: en lugar de llamar a un constructor concreto, el código pide el objeto a un método que las subclases implementan.
Factory Method es un patrón creacional que define un método para crear objetos, pero delega en las subclases la decisión de qué clase concreta instanciar.
El problema
AndesShop arrancó despachando todos los pedidos con un solo transportista propio, AndesExpress. El código que genera el envío al confirmar una compra crea el objeto directamente:
Shipment shipment = new AndesExpressShipment(order);
Esa línea —o una muy parecida— termina apareciendo en el checkout, en el panel de administración y en el job nocturno que genera etiquetas. Funciona bien hasta que AndesShop firma un acuerdo con un segundo transportista, PatagoniaEnvíos, para llegar a zonas donde AndesExpress no opera. Ahora hay que decidir, en cada uno de esos lugares, qué clase instanciar según el destino del pedido — y ese if terminará repetido en cada punto donde antes había un new.
El problema de fondo no es que haya dos transportistas: es que el código que procesa un pedido (calcula el costo, genera la etiqueta, notifica al cliente) no debería tener que saber nada sobre cómo se crea un envío. Esas son dos responsabilidades distintas mezcladas en el mismo lugar.
La solución
Factory Method propone reemplazar la llamada directa a un constructor por una llamada a un método —el factory method— que una subclase se encarga de implementar. El código que procesa el pedido pasa a depender únicamente de una interfaz común (Shipment), nunca de una clase concreta.
classDiagram
class OrderProcessor {
<<abstract>>
+dispatch() void
#createShipment(Order) Shipment*
}
class AndesExpressOrderProcessor {
#createShipment(Order) Shipment
}
class PatagoniaEnviosOrderProcessor {
#createShipment(Order) Shipment
}
class Shipment {
<<interface>>
+generateLabel() String
}
class AndesExpressShipment {
+generateLabel() String
}
class PatagoniaEnviosShipment {
+generateLabel() String
}
OrderProcessor <|-- AndesExpressOrderProcessor
OrderProcessor <|-- PatagoniaEnviosOrderProcessor
Shipment <|.. AndesExpressShipment
Shipment <|.. PatagoniaEnviosShipment
OrderProcessor ..> Shipment : «create»
AndesExpressOrderProcessor ..> AndesExpressShipment : «create»
PatagoniaEnviosOrderProcessor ..> PatagoniaEnviosShipment : «create»Ejemplo en Java
// El "Product": la interfaz que espera el código cliente
interface Shipment {
String generateLabel();
}
// Products concretos: uno por transportista
class AndesExpressShipment implements Shipment {
private final Order order;
AndesExpressShipment(Order order) {
this.order = order;
}
@Override
public String generateLabel() {
return "AndesExpress · " + order.id() + " · " + order.destination();
}
}
class PatagoniaEnviosShipment implements Shipment {
private final Order order;
PatagoniaEnviosShipment(Order order) {
this.order = order;
}
@Override
public String generateLabel() {
return "PatagoniaEnvios · " + order.id() + " · zona extendida";
}
}
// El "Creator": declara el factory method y define qué se hace alrededor de él
abstract class OrderProcessor {
// El factory method. Protegido: es un punto de extensión, no parte de la API pública.
protected abstract Shipment createShipment(Order order);
// El resto del proceso no sabe (ni le importa) qué transportista se usó
public final DispatchResult dispatch(Order order) {
Shipment shipment = createShipment(order);
String label = shipment.generateLabel();
auditLog.record(order.id(), label);
return new DispatchResult(order.id(), label);
}
}
// Creators concretos: cada uno decide qué Product concreto crear
class AndesExpressOrderProcessor extends OrderProcessor {
@Override
protected Shipment createShipment(Order order) {
return new AndesExpressShipment(order);
}
}
class PatagoniaEnviosOrderProcessor extends OrderProcessor {
@Override
protected Shipment createShipment(Order order) {
return new PatagoniaEnviosShipment(order);
}
}
Agregar un tercer transportista significa sumar dos clases y una entrada en el registry — sin tocar dispatch() ni ningún call site.
Cuándo usarlo
- Cuando tu código no puede saber de antemano exactamente con qué clases concretas va a tener que trabajar.
- Cuando querés que quien extienda tu código pueda sumar nuevas variantes de un producto sin modificar la lógica que ya existe.
- Cuando notás la misma lógica de creación de objetos duplicada en varios lugares del código, y esos lugares además comparten qué hacen después de crear el objeto.
Cuándo evitarlo
Si solo tenés (y vas a tener) un único tipo de producto, Factory Method agrega una jerarquía de clases y una capa de indirección que no está resolviendo ningún problema real todavía. Un new directo es perfectamente razonable hasta que aparece una segunda variante concreta.
Ventajas y desventajas
| Ventajas | Desventajas |
|---|---|
| Desacopla el código que usa un objeto de las clases concretas que lo implementan | Puede requerir introducir una nueva jerarquía de subclases (una por cada Creator concreto) |
| Sigue el principio de responsabilidad única: la creación queda en un solo lugar | Si solo hay una variante de producto, la indirección no aporta nada |
| Sigue el principio de abierto/cerrado: sumar una variante nueva no obliga a tocar código existente |
Relación con otros patrones
- Abstract Factory suele implementarse como un conjunto de Factory Methods relacionados entre sí.
- Muchos diseños arrancan con Factory Method —más simple— y evolucionan hacia Abstract Factory, Prototype o Builder a medida que la necesidad de flexibilidad crece.
- Template Method suele apoyarse en uno o más Factory Methods como parte de los pasos del algoritmo que define.
