Qué es Abstract Factory y por qué evolucionó desde Factory Method
Abstract Factory es un patrón creacional que proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas. Mientras que Factory Method define una única operación de creación (subclase que decide qué clase crear), Abstract Factory agrupa múltiples fábricas (o métodos de creación) en una interfaz común para construir objetos que pertenecen a la misma familia. Esto resulta útil cuando necesitas garantizar que los productos usados juntos sean compatibles entre sí.
Evolución: de Factory Method a Abstract Factory (resumen)
Factory Method resuelve la creación de un producto concreto dejando la decisión a una subclase. Abstract Factory se construye encima de esa idea cuando el sistema maneja varias familias de productos (por ejemplo, botones, menús y ventanas para distintos temas gui). En lugar de tener un Factory Method por cada producto y cada familia —lo que complica mantenibilidad— Abstract Factory ofrece una interfaz que agrupa esos factories, haciendo más fácil cambiar toda la familia de productos con una sola inyección.
Cuándo usar Abstract Factory
- Cuando tu sistema debe ser independiente de cómo se crean/ensamblan/representan los productos.
- Cuando debes garantizar que los objetos de una misma familia sean usados juntos (compatibilidad).
- Cuando quieres facilitar el intercambio de familias completas de productos (p. ej. estilos UI: Light/Dark).
- Cuando añadir nuevas familias sin cambiar el cliente es más frecuente que añadir nuevos productos.
Pasos para implementar Abstract Factory
- Definir interfaces/abstracciones para cada tipo de producto (ProductA, ProductB...).
- Definir la interfaz AbstractFactory que expone métodos para crear cada tipo de producto.
- Implementar fábricas concretas (ConcreteFactory1, ConcreteFactory2) que devuelvan implementaciones concretas de cada producto.
- Hacer que el código cliente dependa de las abstracciones (AbstractFactory y Product interfaces) y no de las clases concretas.
- Inyectar la ConcreteFactory deseada (por configuración, inyección de dependencias, variable de entorno) y el cliente construirá familias compatibles.
Ejemplo práctico en TypeScript
// Productos
interface Button { render(): string }
interface Checkbox { render(): string }
// Fábrica abstracta
interface UIFactory {
createButton(): Button
createCheckbox(): Checkbox
}
// Implementaciones concretas - Tema Light
class LightButton implements Button { render() { return 'Button: light style' } }
class LightCheckbox implements Checkbox { render() { return 'Checkbox: light style' } }
class LightFactory implements UIFactory {
createButton(): Button { return new LightButton() }
createCheckbox(): Checkbox { return new LightCheckbox() }
}
// Implementaciones concretas - Tema Dark
class DarkButton implements Button { render() { return 'Button: dark style' } }
class DarkCheckbox implements Checkbox { render() { return 'Checkbox: dark style' } }
class DarkFactory implements UIFactory {
createButton(): Button { return new DarkButton() }
createCheckbox(): Checkbox { return new DarkCheckbox() }
}
// Cliente
function renderUI(factory: UIFactory) {
const btn = factory.createButton()
const chk = factory.createCheckbox()
console.log(btn.render())
console.log(chk.render())
}
// Uso
const theme = process.env.UI_THEME === 'dark' ? new DarkFactory() : new LightFactory()
renderUI(theme)
Ejemplo práctico en Java
// Productos
public interface Button { String render(); }
public interface Checkbox { String render(); }
// Fábrica abstracta
public interface UIFactory {
Button createButton();
Checkbox createCheckbox();
}
// Light
public class LightButton implements Button { public String render(){ return "Button: light style"; } }
public class LightCheckbox implements Checkbox { public String render(){ return "Checkbox: light style"; } }
public class LightFactory implements UIFactory {
public Button createButton(){ return new LightButton(); }
public Checkbox createCheckbox(){ return new LightCheckbox(); }
}
// Dark
public class DarkButton implements Button { public String render(){ return "Button: dark style"; } }
public class DarkCheckbox implements Checkbox { public String render(){ return "Checkbox: dark style"; } }
public class DarkFactory implements UIFactory {
public Button createButton(){ return new DarkButton(); }
public Checkbox createCheckbox(){ return new DarkCheckbox(); }
}
// Cliente (ejemplo de uso)
public class Client {
public static void main(String[] args){
UIFactory factory = System.getenv("UI_THEME") != null && System.getenv("UI_THEME").equals("dark") ? new DarkFactory() : new LightFactory();
System.out.println(factory.createButton().render());
System.out.println(factory.createCheckbox().render());
}
}
Ventajas y desventajas breves
- Ventajas: asegura compatibilidad entre productos, facilita el cambio de familias, mejora la separación de responsabilidades.
- Desventajas: puede aumentar la complejidad y número de clases; añadir nuevos tipos de productos requiere modificar la interfaz AbstractFactory.
¿Por qué usar Abstract Factory en lugar de crear objetos directamente?
Cuando el código crea objetos directamente con new, se vuelve dependiente de clases concretas y difícil de cambiar si en el futuro necesitas una variante diferente. Abstract Factory permite centralizar esas decisiones en un solo lugar, de modo que el resto del sistema no sepa —ni necesite saber— qué tipo exacto de objeto se está usando. Así, puedes cambiar estilos, proveedores o configuraciones sin modificar el código principal.
¿Qué falla si añado un nuevo tipo de producto (p. ej. Slider)?
Debes modificar la interfaz AbstractFactory para exponer createSlider(), y actualizar todas las ConcreteFactory. Esto es la compensación: añadir tipos de producto afecta a todas las fábricas, mientras que añadir nuevas familias no (siempre que ya existan los tipos).
¿Abstract Factory es sólo para interfaces UI?
No. Aunque es popular en UIs para temas, sirve para cualquier conjunto de objetos correlacionados: adaptadores de base de datos, drivers de almacenamiento, estrategias de serialización, etc.
Resumen práctico y recomendaciones
Usa Abstract Factory cuando tengas varias familias de objetos que deben permanecer compatibles y quieras intercambiarlas fácilmente. Si el sistema cambia más por añadir productos que por añadir familias, valora si Factory Method individual o builder combinados son alternativas más simples. En proyectos TypeScript y Java la implementación es directa: define interfaces, agrupa métodos de creación en la fábrica abstracta e inyecta la concrete factory adecuada.