Patrón de Diseño Abstract Factory: El Kit de Creación de Familias 🚗
Imagina que estás programando el sistema de pedidos de un gran grupo automovilístico. Necesitas crear coches, pero tienes dos familias de vehículos muy diferentes: la gama “SEAT” (práctica y familiar) y la gama “CUPRA” (deportiva y de altas prestaciones).

El Patrón de Diseño Abstract Factory es como tener una “Fábrica de Fábricas” o un “Maestro Constructor de Temas”. Su función es permitirte crear familias de objetos relacionados o dependientes (como un motor, un chasis y unos asientos), garantizando que todos pertenezcan a la misma línea de diseño (o la familia SEAT o la familia CUPRA).
Es un patrón creacional del catálogo Gang of Four (GoF), y básicamente resuelve el problema de: “necesito crear varios objetos que tienen que ir juntos, y no quiero que se mezclen piezas de familias distintas”.
¿En qué Consiste?
Este patrón te permite trabajar con interfaces (contratos) sin conocer el nombre de las clases específicas. El código cliente pide “hazme un motor y un asiento” y la fábrica concreta decide si te da los de SEAT o los de CUPRA.
Los Tres Roles Principales
La interfaz o clase abstracta, el contrato principal. Declara los métodos que deben seguir todas las fábricas: "esta fábrica sabe hacer un Motor y un Asiento".
Ejemplo: MarcaCocheFactory
Las implementaciones concretas. Cada una crea productos de una familia específica.
Ejemplo: SeatFactory, CupraFactory
Los objetos que se crean. El Abstract Product es la interfaz genérica (Motor, Asiento) y el Concrete Product es la versión de cada familia.
Ejemplo: MotorSeat, MotorCupra
La Gran Ventaja: Intercambiabilidad Garantizada
Lo guapo del Abstract Factory es que si en tu código necesitas construir un coche completo, solo tienes que decidir al inicio qué Concrete Factory vas a usar (SeatFactory o CupraFactory). Todo lo que pidas después a esa fábrica estará garantizadamente en línea con esa familia, sin tener que cambiar el resto de tu código.
Si mañana el grupo automovilístico compra una nueva marca, solo creas una nueva fábrica y sus productos. El código de montaje no se entera de nada. Eso es la magia.
Ejemplo de la Vida Real: SEAT vs. CUPRA 🏎️
Imagina que quieres montar un coche:
| Componente del Patrón | Ejemplo en Automoción |
|---|---|
| Abstract Factory | MarcaCocheFactory (Métodos: crearMotor(), crearAsiento()) |
| Concrete Factory | SeatFactory o CupraFactory |
| Abstract Product | Motor y Asiento (Interfaces genéricas) |
| Concrete Product | MotorSeat, AsientoCupra, MotorCupra, etc. |
Flujo de Trabajo
- Decisión Inicial: El cliente pide un coche CUPRA.
- Creación de la Fábrica: Tu código crea la Concrete Factory:
new CupraFactory(). - Uso (Cliente): Para construir el coche, tu código (el cliente) solo usa los métodos de la interfaz
MarcaCocheFactory:$motor = $fabrica->crearMotor();$asiento = $fabrica->crearAsiento();
- Resultado: El código cliente recibe un MotorCupra y un AsientoCupra. Si mañana quiere un SEAT, solo cambia la línea de la creación de la fábrica (
new SeatFactory()), y recibirá automáticamente el MotorSeat y el AsientoSeat, sin tocar el código de montaje. ¡Magic! 🧙♀️
Ejemplo de Código en PHP
Este es un ejemplo simplificado de cómo se vería la estructura en PHP, siguiendo el ejemplo de los coches.
1. Las Interfaces (Abstract Factory y Abstract Products)
<?php
// Abstract Products: Los componentes que queremos
interface Motor {
public function obtenerTipoMotor(): string;
}
interface Asiento {
public function obtenerMaterial(): string;
}
// Abstract Factory: El contrato para todas las fábricas
interface MarcaCocheFactory {
public function crearMotor(): Motor;
public function crearAsiento(): Asiento;
}
2. Los Productos Concretos (Las Familias)
<?php
// Productos Concretos de la Familia SEAT
class MotorSeat implements Motor {
public function obtenerTipoMotor(): string {
return "Motor SEAT 1.0 TSI (Eficiente y económico)";
}
}
class AsientoSeat implements Asiento {
public function obtenerMaterial(): string {
return "Asiento SEAT (Tela con diseño práctico)";
}
}
// Productos Concretos de la Familia CUPRA
class MotorCupra implements Motor {
public function obtenerTipoMotor(): string {
return "Motor CUPRA 2.0 TSi (Alto rendimiento y deportivo)";
}
}
class AsientoCupra implements Asiento {
public function obtenerMaterial(): string {
return "Asiento CUPRA (Piel perforada y deportivo)";
}
}
3. Las Fábricas Concretas (Concrete Factories)
<?php
// Concrete Factory para la familia SEAT
class SeatFactory implements MarcaCocheFactory {
public function crearMotor(): Motor {
return new MotorSeat();
}
public function crearAsiento(): Asiento {
return new AsientoSeat();
}
}
// Concrete Factory para la familia CUPRA
class CupraFactory implements MarcaCocheFactory {
public function crearMotor(): Motor {
return new MotorCupra();
}
public function crearAsiento(): Asiento {
return new AsientoCupra();
}
}
4. El Código Cliente (El Montador)
El código cliente solo interactúa con las interfaces y no conoce los nombres de las clases concretas (MotorSeat, AsientoCupra, etc.).
<?php
// El código cliente que usa la fábrica para montar un coche
function montarCoche(MarcaCocheFactory $fabrica): void {
$motor = $fabrica->crearMotor();
$asiento = $fabrica->crearAsiento();
echo "--- Montando Coche --- \n";
echo "Motor: " . $motor->obtenerTipoMotor() . "\n";
echo "Asiento: " . $asiento->obtenerMaterial() . "\n";
echo "------------------------ \n";
}
// Caso 1: Creamos un coche SEAT
$fabricaSeat = new SeatFactory();
montarCoche($fabricaSeat);
// Caso 2: Cambiamos a un coche CUPRA, ¡solo cambiando la fábrica!
$fabricaCupra = new CupraFactory();
montarCoche($fabricaCupra);
/*
Salida esperada:
--- Montando Coche ---
Motor: Motor SEAT 1.0 TSI (Eficiente y económico)
Asiento: Asiento SEAT (Tela con diseño práctico)
------------------------
--- Montando Coche ---
Motor: Motor CUPRA 2.0 TSi (Alto rendimiento y deportivo)
Asiento: Asiento CUPRA (Piel perforada y deportivo)
------------------------
*/
Fíjate en que la función montarCoche() no sabe nada de las clases concretas. Solo trabaja con las interfaces Motor y Asiento. Le da igual si le pasas una SeatFactory, una CupraFactory o una SkodaFactory que crees mañana. Eso es desacoplamiento de verdad.
Otro ejemplo real: Temas de interfaz gráfica 🎨
El ejemplo de los coches está genial para pillar el concepto, pero vamos a ver un caso que te puedes encontrar en un proyecto real. Imagina que tu aplicación soporta temas (Dark Mode y Light Mode) y necesitas crear componentes de UI coherentes:
<?php
// Abstract Products
interface Button {
public function render(): string;
}
interface Input {
public function render(): string;
}
interface Card {
public function render(): string;
}
// Abstract Factory
interface ThemeFactory {
public function createButton(string $text): Button;
public function createInput(string $placeholder): Input;
public function createCard(string $content): Card;
}
// Familia Dark
class DarkButton implements Button {
public function __construct(private string $text) {}
public function render(): string {
return "<button class='bg-gray-800 text-white'>{$this->text}</button>";
}
}
class DarkInput implements Input {
public function __construct(private string $placeholder) {}
public function render(): string {
return "<input class='bg-gray-700 text-white border-gray-600' placeholder='{$this->placeholder}' />";
}
}
class DarkCard implements Card {
public function __construct(private string $content) {}
public function render(): string {
return "<div class='bg-gray-900 border-gray-700 text-gray-200'>{$this->content}</div>";
}
}
// Familia Light
class LightButton implements Button {
public function __construct(private string $text) {}
public function render(): string {
return "<button class='bg-white text-gray-900 border-gray-300'>{$this->text}</button>";
}
}
class LightInput implements Input {
public function __construct(private string $placeholder) {}
public function render(): string {
return "<input class='bg-gray-50 text-gray-900 border-gray-300' placeholder='{$this->placeholder}' />";
}
}
class LightCard implements Card {
public function __construct(private string $content) {}
public function render(): string {
return "<div class='bg-white border-gray-200 text-gray-800'>{$this->content}</div>";
}
}
// Concrete Factories
class DarkThemeFactory implements ThemeFactory {
public function createButton(string $text): Button {
return new DarkButton($text);
}
public function createInput(string $placeholder): Input {
return new DarkInput($placeholder);
}
public function createCard(string $content): Card {
return new DarkCard($content);
}
}
class LightThemeFactory implements ThemeFactory {
public function createButton(string $text): Button {
return new LightButton($text);
}
public function createInput(string $placeholder): Input {
return new LightInput($placeholder);
}
public function createCard(string $content): Card {
return new LightCard($content);
}
}
Y el código cliente:
<?php
function renderLoginForm(ThemeFactory $theme): void {
echo $theme->createCard("Formulario de login")->render() . "\n";
echo $theme->createInput("Email...")->render() . "\n";
echo $theme->createInput("Contraseña...")->render() . "\n";
echo $theme->createButton("Iniciar sesión")->render() . "\n";
}
// ¿Dark mode? Solo cambia la fábrica
$theme = new DarkThemeFactory();
renderLoginForm($theme);
// ¿Light mode? Mismo código, diferente fábrica
$theme = new LightThemeFactory();
renderLoginForm($theme);
Lo potente aquí es que todos los componentes del formulario son visualmente coherentes. No puedes acabar con un botón oscuro y un input claro por error. La fábrica te garantiza que todo pertenece a la misma familia. Si mañana añades un tema “Solarized”, creas una nueva fábrica y sus productos, y el resto del código ni se entera.
Abstract Factory vs. Factory Method 🆚
Esta es la duda más común. Ambos son patrones creacionales y tienen “Factory” en el nombre, pero hacen cosas diferentes:
| Aspecto | Factory Method | Abstract Factory |
|---|---|---|
| ¿Qué crea? | Un solo producto por fábrica | Familias de productos relacionados |
| Número de métodos | Un método de creación | Varios métodos de creación (uno por tipo de producto) |
| Mecanismo | Herencia (subclases sobreescriben el factory method) | Composición (el cliente recibe una fábrica y la usa) |
| Ejemplo | CupraLeonFactory crea solo CupraLeon | CupraFactory crea MotorCupra + AsientoCupra |
| ¿Cuándo usarlo? | Cuando solo creas un tipo de objeto con variantes | Cuando creas varios objetos que tienen que ir juntos |
La forma fácil de recordarlo: Factory Method = una fábrica, un producto. Abstract Factory = una fábrica, varios productos que van en pack.
En la práctica muchas veces un Abstract Factory por dentro usa Factory Methods para crear cada producto. Así que no son excluyentes, se complementan.
Relación con los Principios SOLID
El Abstract Factory encaja perfectamente con varios principios SOLID:
Añadir una familia nueva = crear una fábrica y sus productos. Sin tocar código existente. Es el que más directamente cumple.
El código cliente depende de abstracciones (MarcaCocheFactory, Motor, Asiento), nunca de clases concretas.
Cada fábrica tiene una sola responsabilidad: crear los productos de su familia. La lógica de creación está separada de la lógica de uso.
Cualquier fábrica concreta puede sustituir a la abstracta. Donde esperas MarcaCocheFactory puedes meter SeatFactory, CupraFactory o cualquier otra.
Ventajas y Desventajas ⚖️
- Coherencia garantizada: No puedes mezclar productos de familias distintas por error.
- OCP: Nueva familia = nueva fábrica + nuevos productos. Cero cambios en lo existente.
- Desacoplamiento: El cliente trabaja con interfaces, no con clases concretas.
- Testeable: Puedes inyectar fábricas mock en los tests.
- Intercambiabilidad: Cambiar de familia es cambiar una sola línea.
- Muchas clases: Por cada familia nueva necesitas una fábrica + N productos concretos. Eso escala rápido.
- Añadir un tipo de producto nuevo es doloroso: Si añades
crearChasis()a la interfaz, hay que implementarlo en TODAS las fábricas existentes. - Complejidad innecesaria: Si solo tienes una familia, no necesitas este patrón.
- Jerarquía paralela: Las fábricas crecen en paralelo con los productos.
La desventaja de “añadir un producto nuevo es doloroso” es la más importante. Si añades un método crearSuspension() a MarcaCocheFactory, te toca implementar SuspensionSeat, SuspensionCupra, y modificar todas las fábricas. Tenlo en cuenta antes de elegir este patrón: funciona mejor cuando los tipos de productos son estables pero las familias crecen.
¿Cuándo Usarlo y Cuándo No? 🤔
Úsalo cuando:
- Necesitas crear familias de objetos relacionados que deben ser coherentes entre sí.
- El sistema debe ser independiente de cómo se crean y componen sus productos.
- Quieres que el código cliente trabaje con abstracciones, sin conocer las clases concretas.
- Anticipas que vas a añadir nuevas familias en el futuro (pero los tipos de producto son estables).
NO lo uses cuando:
- Solo tienes una familia de productos. Si no hay variante, no necesitas fábrica abstracta.
- Los productos no tienen relación entre sí. Si un “Motor” y un “Asiento” no necesitan ser de la misma familia, usa Factory Methods individuales.
- El número de tipos de producto cambia frecuentemente. Cada vez que añades un tipo, hay que tocar todas las fábricas.
- Tu caso es simple. Si solo creas un tipo de objeto con variantes, un Factory Method va sobrado.
Recuerda: El Abstract Factory es una herramienta potente, pero como todo patrón, usarlo donde no toca solo añade complejidad. Si no necesitas la coherencia entre familias, no lo metas solo porque queda bonito en el diagrama UML.
Errores comunes al implementarlo
Estos son errores que he visto en código de proyectos reales y que merece la pena tener en cuenta:
1. Confundirlo con Factory Method: Si tu fábrica solo tiene un método de creación, no es un Abstract Factory, es un Factory Method. Abstract Factory crea familias (varios métodos de creación).
2. Meter lógica de negocio en la fábrica: La fábrica solo debe crear objetos, no ejecutar lógica de negocio. Si tu SeatFactory también está calculando precios y descuentos, algo va mal.
3. No usar interfaces para los productos: Si tus productos concretos no implementan una interfaz común, el código cliente no puede trabajar de forma genérica. Pierdes toda la gracia del patrón.
4. Crear fábricas para una sola familia: Si solo tienes la familia SEAT y no hay planes de tener CUPRA, estás añadiendo una capa de abstracción que no necesitas. YAGNI (You Ain’t Gonna Need It).
Ejercicio Práctico 🧠
Para que termines de entender la potencia del Abstract Factory, te propongo este ejercicio:
Tu tarea: Añadir una nueva familia al sistema de coches: la marca SKODA.
- Crea la clase
MotorSkodayAsientoSkoda. (Puedes describirlos como “Robusto y espacioso”). - Asegúrate de que ambas implementen las interfaces
MotoryAsiento. - Crea la Concrete Factory llamada
SkodaFactoryque implementeMarcaCocheFactory. - En el código cliente, instancia la
SkodaFactoryy llama a la funciónmontarCoche()con ella.
Si consigues que tu código imprima la línea de SKODA sin modificar la función montarCoche(), ¡habrás entendido perfectamente el patrón Abstract Factory!
Bonus: Añade un tercer producto a la familia: Suspension. Tendrás que modificar la interfaz MarcaCocheFactory (añadiendo crearSuspension()), todas las fábricas existentes, y crear las implementaciones concretas. Cuando lo hagas, te darás cuenta de por qué la desventaja de “añadir un tipo de producto nuevo es doloroso” es real.
EA, nos vemos en los bares! 🍺 saluditos
Pon a prueba lo aprendido
1. ¿Qué tipo de patrón de diseño es el Abstract Factory?
2. ¿Cuál es la diferencia principal entre Factory Method y Abstract Factory?
3. En el ejemplo de SEAT/CUPRA, ¿qué garantiza el Abstract Factory?
4. ¿Qué principio SOLID cumple más directamente el Abstract Factory?
5. ¿Cuál es la principal desventaja del Abstract Factory?
6. ¿Qué pasa en el código cliente si cambias la fábrica de CupraFactory a SeatFactory?
7. ¿Cuándo NO deberías usar Abstract Factory?
8. En el ejemplo de temas UI (Dark/Light), ¿por qué usamos Abstract Factory y no Factory Methods individuales?