Teoría
Definición
El patrón Abstract Factory (Fábrica abstracta) es un patrón creacional que permite crear familias de objetos relacionados sin especificar sus clases concretas. La clave es encapsular el código de creación en fábricas, de manera que el cliente trabaje solo con interfaces abstractas, manteniendo compatibilidad y flexibilidad.
Cuándo usarlo
- Cuando necesitamos grupos de productos relacionados (botones, menús, muebles, etc.) que deben funcionar juntos.
- Cuando queremos evitar acoplamiento con clases concretas.
- Cuando el sistema debe cambiar de variantes fácilmente (ej. estilos moderno/victoriano, Windows/Linux).
Cuándo no usarlo
- Si solo necesitamos una familia de objetos o un único producto.
- Si la creación de objetos no cambia o no hay variantes.
- Cuando el patrón añade complejidad innecesaria para casos simples.
Estructura (Mermaid)
classDiagram class AbstractFactory { <<interface>> +createChair(): Chair +createSofa(): Sofa }
class ModernFurnitureFactory { +createChair(): Chair +createSofa(): Sofa }
class VictorianFurnitureFactory { +createChair(): Chair +createSofa(): Sofa }
class Chair { <<interface>> +sit() }
class Sofa { <<interface>> +lieDown() }
class ModernChair { +sit() }
class VictorianChair { +sit() }
class ModernSofa { +lieDown() }
class VictorianSofa { +lieDown() }
AbstractFactory <|.. ModernFurnitureFactory AbstractFactory <|.. VictorianFurnitureFactory Chair <|.. ModernChair Chair <|.. VictorianChair Sofa <|.. ModernSofa Sofa <|.. VictorianSofaEjemplo en Spring Boot (Java)
// Interfaces de productospublic interface Chair { void sit();}
public interface Sofa { void lieDown();}
// Implementaciones concretaspublic class ModernChair implements Chair { public void sit() { System.out.println("Sentado en una silla moderna."); }}
public class VictorianChair implements Chair { public void sit() { System.out.println("Sentado en una silla victoriana."); }}
public class ModernSofa implements Sofa { public void lieDown() { System.out.println("Acostado en un sofá moderno."); }}
public class VictorianSofa implements Sofa { public void lieDown() { System.out.println("Acostado en un sofá victoriano."); }}
// Abstract Factorypublic interface FurnitureFactory { Chair createChair(); Sofa createSofa();}
// Fábricas concretaspublic class ModernFurnitureFactory implements FurnitureFactory { public Chair createChair() { return new ModernChair(); } public Sofa createSofa() { return new ModernSofa(); }}
public class VictorianFurnitureFactory implements FurnitureFactory { public Chair createChair() { return new VictorianChair(); } public Sofa createSofa() { return new VictorianSofa(); }}
// Cliente (Spring Boot Service)@Servicepublic class FurnitureService { private final FurnitureFactory factory;
public FurnitureService() { // Aquí podría decidirse por configuración this.factory = new ModernFurnitureFactory(); }
public void demo() { Chair chair = factory.createChair(); Sofa sofa = factory.createSofa(); chair.sit(); sofa.lieDown(); }}Ejemplo en Django (Python)
from abc import ABC, abstractmethod
# Interfacesclass Chair(ABC): @abstractmethod def sit(self): pass
class Sofa(ABC): @abstractmethod def lie_down(self): pass
# Implementaciones concretasclass ModernChair(Chair): def sit(self): return "Sentado en una silla moderna"
class VictorianChair(Chair): def sit(self): return "Sentado en una silla victoriana"
class ModernSofa(Sofa): def lie_down(self): return "Acostado en un sofá moderno"
class VictorianSofa(Sofa): def lie_down(self): return "Acostado en un sofá victoriano"
# Abstract Factoryclass FurnitureFactory(ABC): @abstractmethod def create_chair(self) -> Chair: pass
@abstractmethod def create_sofa(self) -> Sofa: pass
# Fábricas concretasclass ModernFurnitureFactory(FurnitureFactory): def create_chair(self): return ModernChair() def create_sofa(self): return ModernSofa()
class VictorianFurnitureFactory(FurnitureFactory): def create_chair(self): return VictorianChair() def create_sofa(self): return VictorianSofa()
# Cliente en Django (ejemplo en views.py)from django.http import JsonResponse
def furniture_demo(request): factory = ModernFurnitureFactory() # Podría cambiarse dinámicamente chair = factory.create_chair() sofa = factory.create_sofa() return JsonResponse({ "chair": chair.sit(), "sofa": sofa.lie_down() })Resumen
- Abstract Factory permite crear familias de productos consistentes sin acoplar el código a implementaciones concretas.
- Es útil en aplicaciones que necesitan variantes intercambiables (ej. estilos, SO, catálogos).
- Se aplica tanto en Java (Spring Boot) como en Python (Django) con interfaces y fábricas concretas.
Práctica con Spring Boot
Paso 1: Creación del Proyecto en IntelliJ IDEA 🚀
Primero, vamos a crear un proyecto nuevo y limpio.
-
Abre IntelliJ IDEA y ve a File > New > Project….
-
En la ventana que aparece, selecciona Spring Initializr en el panel izquierdo.
-
Configura los metadatos de tu proyecto. Puedes usar estos valores:
- Name:
abstract-factory-ejemplo - Location: La carpeta donde quieras guardarlo.
- Language: Java
- Type: Gradle - Groovy (o Maven si lo prefieres, ambos funcionarán).
- Group:
com.example.solid - Artifact:
abstract-factory-ejemplo - JDK: Asegúrate de seleccionar la versión 17 (o superior).
- Packaging: Jar
- Name:
-
Haz clic en Next.
-
Ahora, elige las dependencias. Para este proyecto, solo necesitas una:
- Busca y selecciona Spring Web.
-
Haz clic en Create. IntelliJ generará la estructura del proyecto y descargará las dependencias.
Paso 2: Estructura de Paquetes 📂
Una buena organización es clave. Dentro de src/main/java/com/example/solid/abstractfactoryejemplo, crearemos los siguientes paquetes para mantener nuestro código ordenado:
products: Contendrá las interfaces de los productos (ej.Silla) y sus implementaciones concretas (ej.SillaModerna).factory: Contendrá la interfaz de la fábrica abstracta (MuebleriaFactory) y sus implementaciones concretas (MuebleriaModernaFactory).controller: Contendrá nuestroRestController, que actuará como el cliente del patrón.
Paso 3: Codificación del Patrón Abstract Factory 🛋️
Ahora vamos a escribir el código, siguiendo la estructura que definimos.
3.1. Productos Abstractos (Interfaces)
Dentro del paquete products, crea estas dos interfaces. Serán el contrato para nuestras familias de objetos.
Silla.java
package com.example.solid.abstractfactoryejemplo.products;
public interface Silla { String sentarse();}Sofa.java
package com.example.solid.abstractfactoryejemplo.products;
public interface Sofa { String recostarse();}3.2. Productos Concretos (Implementaciones)
En el mismo paquete products, crea las cuatro clases que implementan las interfaces anteriores. Estos son los objetos reales que construiremos.
SillaModerna.java
package com.example.solid.abstractfactoryejemplo.products;
public class SillaModerna implements Silla { @Override public String sentarse() { return "Sentado cómodamente en una silla moderna."; }}SillaVictoriana.java
package com.example.solid.abstractfactoryejemplo.products;
public class SillaVictoriana implements Silla { @Override public String sentarse() { return "Sentado con elegancia en una silla victoriana."; }}SofaModerno.java
package com.example.solid.abstractfactoryejemplo.products;
public class SofaModerno implements Sofa { @Override public String recostarse() { return "Recostado en un sofá de diseño minimalista."; }}SofaVictoriano.java
package com.example.solid.abstractfactoryejemplo.products;
public class SofaVictoriano implements Sofa { @Override public String recostarse() { return "Descansando sobre un sofá victoriano de terciopelo."; }}3.3. Fábrica Abstracta (Interfaz)
Ahora, en el paquete factory, crea la interfaz que define qué productos pueden crear nuestras fábricas.
MuebleriaFactory.java
package com.example.solid.abstractfactoryejemplo.factory;
import com.example.solid.abstractfactoryejemplo.products.Silla;import com.example.solid.abstractfactoryejemplo.products.Sofa;
public interface MuebleriaFactory { Silla crearSilla(); Sofa crearSofa();}3.4. Fábricas Concretas (Implementaciones)
En el mismo paquete factory, crea las fábricas que saben cómo construir una familia de productos específica (moderna o victoriana). Las anotamos como @Component de Spring para que el sistema las reconozca y las podamos inyectar después.
MuebleriaModernaFactory.java
package com.example.solid.abstractfactoryejemplo.factory;
import com.example.solid.abstractfactoryejemplo.products.*;import org.springframework.stereotype.Component;
@Component("moderna") // Nombre clave para identificar esta fábricapublic class MuebleriaModernaFactory implements MuebleriaFactory { @Override public Silla crearSilla() { return new SillaModerna(); }
@Override public Sofa crearSofa() { return new SofaModerno(); }}MuebleriaVictorianaFactory.java
package com.example.solid.abstractfactoryejemplo.factory;
import com.example.solid.abstractfactoryejemplo.products.*;import org.springframework.stereotype.Component;
@Component("victoriana") // Nombre clave para esta otra fábricapublic class MuebleriaVictorianaFactory implements MuebleriaFactory { @Override public Silla crearSilla() { return new SillaVictoriana(); }
@Override public Sofa crearSofa() { return new SofaVictoriano(); }}Paso 4: Crear el Cliente (Controlador REST) 🌐
Finalmente, en el paquete controller, crearemos la clase que utiliza las fábricas para construir los productos. Usaremos la misma técnica elegante del ejemplo anterior para que Spring nos inyecte todas las fábricas disponibles.
MuebleriaController.java
package com.example.solid.abstractfactoryejemplo.controller;
import com.example.solid.abstractfactoryejemplo.factory.MuebleriaFactory;import com.example.solid.abstractfactoryejemplo.products.Silla;import com.example.solid.abstractfactoryejemplo.products.Sofa;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
import java.util.Map;import java.util.Optional;
@RestController@RequestMapping("/api/muebles")public class MuebleriaController {
// Spring inyecta un mapa con todas las fábricas. La clave es el nombre // que pusimos en @Component ("moderna", "victoriana"). private final Map<String, MuebleriaFactory> factories;
@Autowired public MuebleriaController(Map<String, MuebleriaFactory> factories) { this.factories = factories; }
@GetMapping("/crear/{estilo}") public ResponseEntity<?> crearMuebles(@PathVariable String estilo) { // Buscamos la fábrica correcta según el "estilo" de la URL. MuebleriaFactory factory = factories.get(estilo);
if (factory == null) { return ResponseEntity.badRequest().body("Estilo de mueblería no disponible: " + estilo); }
// Usamos la fábrica para crear la familia de productos. Silla silla = factory.crearSilla(); Sofa sofa = factory.crearSofa();
// Creamos una respuesta con los mensajes de los productos creados. Map<String, String> response = Map.of( "silla", silla.sentarse(), "sofa", sofa.recostarse(), "fabrica_usada", factory.getClass().getSimpleName() );
return ResponseEntity.ok(response); }}Paso 5: Probar la Aplicación ✅
¡Todo está listo!
- Ejecuta tu aplicación desde la clase principal
AbstractFactoryEjemploApplication. - Usa un navegador o una herramienta como Postman para probar los siguientes endpoints:
-
Para obtener muebles modernos:
http://localhost:8080/api/muebles/crear/modernaRespuesta esperada (JSON):
{"silla": "Sentado cómodamente en una silla moderna.","sofa": "Recostado en un sofá de diseño minimalista.","fabrica_usada": "MuebleriaModernaFactory"} -
Para obtener muebles victorianos:
http://localhost:8080/api/muebles/crear/victorianaRespuesta esperada (JSON):
{"silla": "Sentado con elegancia en una silla victoriana.","sofa": "Descansando sobre un sofá victoriano de terciopelo.","fabrica_usada": "MuebleriaVictorianaFactory"} -
Para un estilo no existente:
http://localhost:8080/api/muebles/crear/rusticaRespuesta esperada:
Estilo de mueblería no disponible: rustica
Práctica con Django
Paso 1: Creación del Proyecto en PyCharm 🚀
Primero, vamos a crear un proyecto Django nuevo y limpio.
- Abre PyCharm (versión Professional) y ve a File > New > Project….
- En la ventana que aparece, selecciona Django en el panel izquierdo.
- Configura tu proyecto:
- Location: Elige una carpeta y nombra el proyecto, por ejemplo,
abstract_factory_django. - New environment using: Selecciona Virtualenv. Esto creará un entorno virtual aislado para tu proyecto.
- Application name: Puedes nombrar tu primera aplicación
core.
- Location: Elige una carpeta y nombra el proyecto, por ejemplo,
- Haz clic en Create. PyCharm configurará el entorno, instalará Django y generará la estructura básica del proyecto.
Paso 2: Estructura de Archivos y Aplicación Django 📂
En Django, la lógica se organiza en “apps”. Crearemos una app específica para nuestra mueblería.
- Abre la Terminal dentro de PyCharm (View > Tool Windows > Terminal).
- Ejecuta el siguiente comando para crear una nueva app llamada
muebleria:Terminal window python manage.py startapp muebleria - Ahora, registra esta nueva app en tu proyecto. Abre el archivo
abstract_factory_django/settings.pyy añade'muebleria'a la lista deINSTALLED_APPS:abstract_factory_django/settings.py INSTALLED_APPS = [# ... otras apps'django.contrib.staticfiles','muebleria', # Añade tu app aquí]
Dentro de la carpeta muebleria, organizaremos nuestro patrón en diferentes archivos: products.py, factories.py, y modificaremos views.py.
Paso 3: Codificación del Patrón Abstract Factory 🛋️
Ahora vamos a escribir el código Python. Usaremos el módulo abc para crear clases abstractas, que es el equivalente a las interfaces de Java para este propósito.
3.1. Productos Abstractos
-
Dentro de la carpeta
muebleria, crea un nuevo archivo llamadoproducts.py. -
Añade el siguiente código con las clases base para nuestros productos:
muebleria/products.pyfrom abc import ABC, abstractmethod# Producto Abstracto 1class Silla(ABC):@abstractmethoddef sentarse(self) -> str:pass# Producto Abstracto 2class Sofa(ABC):@abstractmethoddef recostarse(self) -> str:pass# --- Implementaciones de la familia Moderna ---class SillaModerna(Silla):def sentarse(self) -> str:return "Sentado cómodamente en una silla moderna."class SofaModerno(Sofa):def recostarse(self) -> str:return "Recostado en un sofá de diseño minimalista."# --- Implementaciones de la familia Victoriana ---class SillaVictoriana(Silla):def sentarse(self) -> str:return "Sentado con elegancia en una silla victoriana."class SofaVictoriano(Sofa):def recostarse(self) -> str:return "Descansando sobre un sofá victoriano de terciopelo."
3.2. Fábricas (Abstracta y Concretas)
-
Dentro de la carpeta
muebleria, crea otro archivo nuevo llamadofactories.py. -
Añade el siguiente código para definir las fábricas:
muebleria/factories.pyfrom abc import ABC, abstractmethodfrom .products import (Silla, Sofa,SillaModerna, SofaModerno,SillaVictoriana, SofaVictoriano)# La Fábrica Abstractaclass MuebleriaFactory(ABC):@abstractmethoddef crear_silla(self) -> Silla:pass@abstractmethoddef crear_sofa(self) -> Sofa:pass# Fábrica Concreta 1: para muebles modernosclass MuebleriaModernaFactory(MuebleriaFactory):def crear_silla(self) -> Silla:return SillaModerna()def crear_sofa(self) -> Sofa:return SofaModerno()# Fábrica Concreta 2: para muebles victorianosclass MuebleriaVictorianaFactory(MuebleriaFactory):def crear_silla(self) -> Silla:return SillaVictoriana()def crear_sofa(self) -> Sofa:return SofaVictoriano()
Paso 4: Crear el Cliente (La Vista de Django) 🌐
El “cliente” en Django será una vista que recibe la petición HTTP, elige la fábrica correcta y devuelve una respuesta JSON.
Modifica el archivo muebleria/views.py.
muebleria/views.py
from django.http import JsonResponsefrom .factories import MuebleriaModernaFactory, MuebleriaVictorianaFactory
# Un mapa para registrar nuestras fábricas disponibles.FABRICAS = { "moderna": MuebleriaModernaFactory, "victoriana": MuebleriaVictorianaFactory,}
def crear_muebles(request, estilo: str): """ Esta vista actúa como el cliente del patrón Abstract Factory. """ # 1. Busca la clase de la fábrica correcta. fabrica_class = FABRICAS.get(estilo)
if not fabrica_class: return JsonResponse({"error": f"Estilo de mueblería no disponible: {estilo}"}, status=400)
# 2. Instancia la fábrica seleccionada. fabrica = fabrica_class()
# 3. Usa la fábrica para crear la familia de productos. silla = fabrica.crear_silla() sofa = fabrica.crear_sofa()
# 4. Construye y devuelve la respuesta. response_data = { "silla": silla.sentarse(), "sofa": sofa.recostarse(), "fabrica_usada": fabrica.__class__.__name__ } return JsonResponse(response_data)Paso 5: Configurar las URLs 🔗
Necesitamos decirle a Django qué URL debe activar nuestra vista.
-
Dentro de la carpeta
muebleria, crea un nuevo archivourls.py.muebleria/urls.pyfrom django.urls import pathfrom . import viewsurlpatterns = [path('crear/<str:estilo>/', views.crear_muebles, name='crear_muebles'),] -
Ahora, incluye estas URLs en el archivo principal del proyecto.
abstract_factory_django/urls.pyfrom django.contrib import adminfrom django.urls import path, include # Asegúrate de importar "include"urlpatterns = [path('admin/', admin.site.urls),path('api/muebles/', include('muebleria.urls')), # Conecta las URLs de tu app]
Paso 6: Probar la Aplicación ✅
¡Todo está configurado!
- En la terminal de PyCharm, ejecuta el servidor de desarrollo de Django:
Terminal window python manage.py runserver - Usa un navegador o
curlpara probar los siguientes endpoints:
-
Para obtener muebles modernos:
http://127.0.0.1:8000/api/muebles/crear/moderna/Respuesta esperada (JSON):
{"silla": "Sentado cómodamente en una silla moderna.","sofa": "Recostado en un sofá de diseño minimalista.","fabrica_usada": "MuebleriaModernaFactory"} -
Para obtener muebles victorianos:
http://127.0.0.1:8000/api/muebles/crear/victoriana/Respuesta esperada (JSON):
{"silla": "Sentado con elegancia en una silla victoriana.","sofa": "Descansando sobre un sofá victoriano de terciopelo.","fabrica_usada": "MuebleriaVictorianaFactory"} -
Para un estilo no existente:
http://127.0.0.1:8000/api/muebles/crear/rustica/Respuesta esperada (JSON con código 400):
{"error": "Estilo de mueblería no disponible: rustica"}