Patrones de Diseño Creacionales en C++
Aprende patrones de diseño creacionales como Singleton, Factory, Builder y Prototype con ejemplos prácticos en C++ moderno, aprovechando smart pointers, RAII y características del lenguaje.
¡Domina los patrones de diseño creacionales en C++! En este tutorial especializado te guiaré paso a paso para que aprendas los patrones fundamentales de creación de objetos, incluyendo Singleton, Factory Method, Builder y Prototype, con ejemplos prácticos y casos de uso reales en C++ moderno.
Objetivo: Aprender los patrones de diseño creacionales más importantes, sus implementaciones en C++, aprovechando las fortalezas del lenguaje como smart pointers, RAII, templates y gestión automática de memoria, con ventajas, desventajas y cuándo aplicarlos en proyectos reales.
¿Por qué C++ para patrones creacionales?
- Type Safety: El sistema de tipos fuerte de C++ previene errores en tiempo de compilación
- Performance: Zero-cost abstractions y optimizaciones del compilador
- Memory Management: RAII y smart pointers para gestión segura y automática
- Templates: Programación genérica para patrones reutilizables
- Modern C++: Uso de C++11/14/17/20 features como
std::unique_ptr,std::shared_ptr,std::optional
Índice
- Paso 1: ¿Qué son los patrones creacionales?
- Paso 2: Singleton - Una instancia única
- Paso 3: Factory Method - Creación flexible
- Paso 4: Abstract Factory - Familias de objetos
- Paso 5: Builder - Construcción paso a paso
- Paso 6: Prototype - Clonación de objetos
- Paso 7: Comparación y selección de patrones
- Paso 8: Proyecto práctico - Sistema de configuración
- Paso 9: Antipatrones y errores comunes
- Conclusión
- 💡 Tip Importante
Paso 1: ¿Qué son los patrones creacionales?
Los patrones de diseño creacionales se centran en el proceso de creación de objetos, proporcionando mecanismos flexibles para instanciar clases. Estos patrones abstraen el proceso de creación, haciendo que el sistema sea independiente de cómo se crean, componen y representan los objetos.
¿Por qué son importantes?
- Flexibilidad: Permiten cambiar las clases concretas sin modificar el código cliente
- Reutilización: Facilitan la creación de objetos complejos
- Mantenimiento: Separan la lógica de creación de la lógica de negocio
- Testing: Facilitan el uso de mocks y stubs
Clasificación de patrones creacionales
| Patrón | Propósito | Complejidad |
|---|---|---|
| Singleton | Una instancia única global | Baja |
| Factory Method | Delegar creación a subclases | Media |
| Abstract Factory | Crear familias de objetos | Alta |
| Builder | Construir objetos complejos paso a paso | Media |
| Prototype | Clonar objetos existentes | Baja |
Paso 2: Singleton - Una instancia única
El patrón Singleton asegura que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. En C++, es especialmente útil para recursos compartidos como conexiones a base de datos, configuraciones globales o gestores de logs, aprovechando las características del lenguaje como RAII y smart pointers.
Implementación básica con Meyer Singleton
#include <iostream>
#include <string>
class Singleton {
private:
std::string data;
static Singleton* instance;
// Constructor privado
Singleton() : data("Datos del singleton") {}
// Destructor privado
~Singleton() = default;
// Prevenir copia y asignación
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// Método estático para obtener la instancia
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
// Método para limpiar la instancia (opcional)
static void destroyInstance() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
void setData(const std::string& newData) {
data = newData;
}
std::string getData() const {
return data;
}
void showMessage() const {
std::cout << "Singleton dice: " << data << std::endl;
}
};
// Inicializar el puntero estático
Singleton* Singleton::instance = nullptr;
// Uso
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
std::cout << "s1 y s2 son la misma instancia: " << (s1 == s2) << std::endl;
std::cout << "s1 data: " << s1->getData() << std::endl;
std::cout << "s2 data: " << s2->getData() << std::endl;
s1->setData("Modificado");
std::cout << "Después de modificar s1:" << std::endl;
std::cout << "s1 data: " << s1->getData() << std::endl;
std::cout << "s2 data: " << s2->getData() << std::endl; // También cambió
// Limpiar la instancia
Singleton::destroyInstance();
return 0;
}
Singleton thread-safe con std::call_once
#include <iostream>
#include <string>
#include <mutex>
#include <memory>
class SingletonThreadSafe {
private:
static std::unique_ptr<SingletonThreadSafe> instance;
static std::once_flag initFlag;
std::string data;
SingletonThreadSafe() : data("Datos del singleton thread-safe") {}
public:
// Constructor de copia y asignación eliminados
SingletonThreadSafe(const SingletonThreadSafe&) = delete;
SingletonThreadSafe& operator=(const SingletonThreadSafe&) = delete;
static SingletonThreadSafe* getInstance() {
std::call_once(initFlag, []() {
instance = std::unique_ptr<SingletonThreadSafe>(new SingletonThreadSafe());
});
return instance.get();
}
void setData(const std::string& newData) {
data = newData;
}
std::string getData() const {
return data;
}
};
// Inicializar los miembros estáticos
std::unique_ptr<SingletonThreadSafe> SingletonThreadSafe::instance;
std::once_flag SingletonThreadSafe::initFlag;
// Uso en entorno multi-hilo
#include <thread>
#include <vector>
void worker(int id) {
SingletonThreadSafe* singleton = SingletonThreadSafe::getInstance();
std::cout << "Thread " << id << " - Instancia: " << singleton << std::endl;
}
int main() {
const int numThreads = 5;
std::vector<std::thread> threads;
// Crear múltiples threads
for (int i = 0; i < numThreads; ++i) {
threads.emplace_back(worker, i);
}
// Esperar a que todos los threads terminen
for (auto& thread : threads) {
thread.join();
}
return 0;
}
Singleton con smart pointers y RAII
#include <iostream>
#include <string>
#include <memory>
class DatabaseConnection {
private:
std::string connectionString;
static std::shared_ptr<DatabaseConnection> instance;
static std::mutex mutex;
DatabaseConnection(const std::string& connStr) : connectionString(connStr) {
std::cout << "Conectando a: " << connectionString << std::endl;
}
public:
// Constructor de copia y asignación eliminados
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
static std::shared_ptr<DatabaseConnection> getInstance(const std::string& connStr = "postgresql://user:pass@localhost/db") {
std::lock_guard<std::mutex> lock(mutex);
if (!instance) {
instance = std::shared_ptr<DatabaseConnection>(new DatabaseConnection(connStr));
}
return instance;
}
std::string query(const std::string& sql) {
return "Ejecutando: " + sql + " en " + connectionString;
}
~DatabaseConnection() {
std::cout << "Desconectando de: " << connectionString << std::endl;
}
};
// Inicializar los miembros estáticos
std::shared_ptr<DatabaseConnection> DatabaseConnection::instance;
std::mutex DatabaseConnection::mutex;
// Uso
int main() {
auto db1 = DatabaseConnection::getInstance();
auto db2 = DatabaseConnection::getInstance();
std::cout << "db1 y db2 son la misma instancia: " << (db1 == db2) << std::endl;
std::cout << db1->query("SELECT * FROM users") << std::endl;
std::cout << db2->query("SELECT * FROM products") << std::endl;
// Los smart pointers manejan automáticamente la destrucción
return 0;
}
Casos de uso comunes en C++
- Configuración de aplicación:
AppConfigcon lazy initialization - Conexión a base de datos:
DatabaseConnectioncon RAII - Sistema de logging:
Loggercon thread safety - Gestor de caché:
CacheManagercon smart pointers - Pool de conexiones:
ConnectionPoolcon singleton thread-safe
Paso 3: Factory Method - Creación flexible
El patrón Factory Method define una interfaz para crear objetos, pero permite a las subclases decidir qué clase instanciar. En C++, este patrón es especialmente poderoso gracias a las plantillas (templates) y el polimorfismo, proporcionando type safety y performance optimizado.
Implementación básica con clases abstractas
#include <iostream>
#include <memory>
#include <string>
// Producto abstracto
class Producto {
public:
virtual ~Producto() = default;
virtual std::string operacion() const = 0;
};
// Productos concretos
class ProductoConcretoA : public Producto {
public:
std::string operacion() const override {
return "Resultado del Producto A";
}
};
class ProductoConcretoB : public Producto {
public:
std::string operacion() const override {
return "Resultado del Producto B";
}
};
// Creador abstracto
class Creador {
public:
virtual ~Creador() = default;
// Factory Method
virtual std::unique_ptr<Producto> crearProducto() const = 0;
// Método que usa el Factory Method
std::string algunaOperacion() const {
auto producto = crearProducto();
return "Creador: " + producto->operacion();
}
};
// Creadores concretos
class CreadorConcretoA : public Creador {
public:
std::unique_ptr<Producto> crearProducto() const override {
return std::make_unique<ProductoConcretoA>();
}
};
class CreadorConcretoB : public Creador {
public:
std::unique_ptr<Producto> crearProducto() const override {
return std::make_unique<ProductoConcretoB>();
}
};
// Función cliente
void cliente(const Creador& creador) {
std::cout << creador.algunaOperacion() << std::endl;
}
// Uso
int main() {
CreadorConcretoA creadorA;
CreadorConcretoB creadorB;
cliente(creadorA); // "Creador: Resultado del Producto A"
cliente(creadorB); // "Creador: Resultado del Producto B"
return 0;
}
Factory Method con templates para type safety
#include <iostream>
#include <memory>
#include <string>
// Transporte abstracto
class Transporte {
public:
virtual ~Transporte() = default;
virtual std::string entregar() const = 0;
};
// Transportes concretos
class Camion : public Transporte {
public:
std::string entregar() const override {
return "Entregando por tierra en camión";
}
};
class Barco : public Transporte {
public:
std::string entregar() const override {
return "Entregando por mar en barco";
}
};
class Avion : public Transporte {
public:
std::string entregar() const override {
return "Entregando por aire en avión";
}
};
// Factory Method con templates
template <typename T>
class Logistica {
public:
static std::unique_ptr<Transporte> crearTransporte() {
return std::make_unique<T>();
}
std::string planificarEntrega() const {
auto transporte = crearTransporte<T>();
return "Logística: " + transporte->entregar();
}
};
// Función cliente usando templates
template <typename T>
void clienteLogistica() {
Logistica<T> logistica;
std::cout << logistica.planificarEntrega() << std::endl;
}
// Uso
int main() {
std::cout << "=== Factory Method con Templates ===" << std::endl;
clienteLogistica<Camion>(); // "Logística: Entregando por tierra en camión"
clienteLogistica<Barco>(); // "Logística: Entregando por mar en barco"
clienteLogistica<Avion>(); // "Logística: Entregando por aire en avión"
return 0;
}
Factory Method con parámetros usando std::function
#include <iostream>
#include <memory>
#include <string>
#include <functional>
#include <unordered_map>
// Producto
class Documento {
public:
virtual ~Documento() = default;
virtual std::string obtenerTipo() const = 0;
virtual void imprimir() const = 0;
};
// Documentos concretos
class DocumentoPDF : public Documento {
public:
std::string obtenerTipo() const override { return "PDF"; }
void imprimir() const override { std::cout << "Imprimiendo documento PDF" << std::endl; }
};
class DocumentoWord : public Documento {
public:
std::string obtenerTipo() const override { return "Word"; }
void imprimir() const override { std::cout << "Imprimiendo documento Word" << std::endl; }
};
class DocumentoExcel : public Documento {
public:
std::string obtenerTipo() const override { return "Excel"; }
void imprimir() const override { std::cout << "Imprimiendo documento Excel" << std::endl; }
};
// Factory con std::function para mayor flexibilidad
class FabricaDocumentos {
private:
std::unordered_map<std::string, std::function<std::unique_ptr<Documento>()>> fabricas;
public:
FabricaDocumentos() {
// Registrar las fábricas
fabricas["pdf"] = []() { return std::make_unique<DocumentoPDF>(); };
fabricas["word"] = []() { return std::make_unique<DocumentoWord>(); };
fabricas["excel"] = []() { return std::make_unique<DocumentoExcel>(); };
}
std::unique_ptr<Documento> crearDocumento(const std::string& tipo) {
auto it = fabricas.find(tipo);
if (it != fabricas.end()) {
return it->second();
}
throw std::invalid_argument("Tipo de documento no soportado: " + tipo);
}
// Método para registrar nuevos tipos dinámicamente
void registrarTipo(const std::string& tipo, std::function<std::unique_ptr<Documento>()> fabrica) {
fabricas[tipo] = std::move(fabrica);
}
};
// Uso
int main() {
FabricaDocumentos fabrica;
auto pdf = fabrica.crearDocumento("pdf");
auto word = fabrica.crearDocumento("word");
auto excel = fabrica.crearDocumento("excel");
std::cout << "Tipo: " << pdf->obtenerTipo() << std::endl;
pdf->imprimir();
std::cout << "Tipo: " << word->obtenerTipo() << std::endl;
word->imprimir();
std::cout << "Tipo: " << excel->obtenerTipo() << std::endl;
excel->imprimir();
return 0;
}
Paso 4: Abstract Factory - Familias de objetos
El patrón Abstract Factory proporciona una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas. En C++, este patrón es especialmente útil para crear familias de objetos que deben trabajar juntos, aprovechando el type safety del lenguaje y las características de templates.
Implementación del patrón con interfaces abstractas
#include <iostream>
#include <memory>
#include <string>
// Interfaces abstractas de productos
class Silla {
public:
virtual ~Silla() = default;
virtual std::string sentarse() const = 0;
};
class Mesa {
public:
virtual ~Mesa() = default;
virtual std::string usar() const = 0;
};
class Sofa {
public:
virtual ~Sofa() = default;
virtual std::string descansar() const = 0;
};
// Productos concretos - Estilo Moderno
class SillaModerna : public Silla {
public:
std::string sentarse() const override {
return "Sentándose en silla moderna";
}
};
class MesaModerna : public Mesa {
public:
std::string usar() const override {
return "Usando mesa moderna";
}
};
class SofaModerno : public Sofa {
public:
std::string descansar() const override {
return "Descansando en sofá moderno";
}
};
// Productos concretos - Estilo Victoriano
class SillaVictoriana : public Silla {
public:
std::string sentarse() const override {
return "Sentándose en silla victoriana";
}
};
class MesaVictoriana : public Mesa {
public:
std::string usar() const override {
return "Usando mesa victoriana";
}
};
class SofaVictoriano : public Sofa {
public:
std::string descansar() const override {
return "Descansando en sofá victoriano";
}
};
// Abstract Factory
class FabricaMuebles {
public:
virtual ~FabricaMuebles() = default;
virtual std::unique_ptr<Silla> crearSilla() const = 0;
virtual std::unique_ptr<Mesa> crearMesa() const = 0;
virtual std::unique_ptr<Sofa> crearSofa() const = 0;
};
// Concrete Factories
class FabricaModerna : public FabricaMuebles {
public:
std::unique_ptr<Silla> crearSilla() const override {
return std::make_unique<SillaModerna>();
}
std::unique_ptr<Mesa> crearMesa() const override {
return std::make_unique<MesaModerna>();
}
std::unique_ptr<Sofa> crearSofa() const override {
return std::make_unique<SofaModerno>();
}
};
class FabricaVictoriana : public FabricaMuebles {
public:
std::unique_ptr<Silla> crearSilla() const override {
return std::make_unique<SillaVictoriana>();
}
std::unique_ptr<Mesa> crearMesa() const override {
return std::make_unique<MesaVictoriana>();
}
std::unique_ptr<Sofa> crearSofa() const override {
return std::make_unique<SofaVictoriano>();
}
};
// Función cliente
void clienteFabrica(const FabricaMuebles& fabrica) {
auto silla = fabrica.crearSilla();
auto mesa = fabrica.crearMesa();
auto sofa = fabrica.crearSofa();
std::cout << "Conjunto creado:" << std::endl;
std::cout << "- " << silla->sentarse() << std::endl;
std::cout << "- " << mesa->usar() << std::endl;
std::cout << "- " << sofa->descansar() << std::endl;
}
// Uso
int main() {
std::cout << "=== Estilo Moderno ===" << std::endl;
FabricaModerna fabricaModerna;
clienteFabrica(fabricaModerna);
std::cout << "\n=== Estilo Victoriano ===" << std::endl;
FabricaVictoriana fabricaVictoriana;
clienteFabrica(fabricaVictoriana);
return 0;
}
Abstract Factory con templates para mayor flexibilidad
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
// Concept para productos (C++20)
template <typename T>
concept EsProducto = requires(T t) {
{ t.operacion() } -> std::convertible_to<std::string>;
};
// Abstract Factory usando templates
template <typename ProductoBase>
class FabricaAbstracta {
public:
virtual ~FabricaAbstracta() = default;
virtual std::unique_ptr<ProductoBase> crearProducto() const = 0;
};
// Productos para diferentes familias
class Vehiculo {
public:
virtual ~Vehiculo() = default;
virtual std::string conducir() const = 0;
};
class VehiculoElectrico : public Vehiculo {
public:
std::string conducir() const override {
return "Conduciendo vehículo eléctrico silenciosamente";
}
};
class VehiculoGasolina : public Vehiculo {
public:
std::string conducir() const override {
return "Conduciendo vehículo a gasolina con motor potente";
}
};
// Fábricas concretas
class FabricaVehiculosElectricos : public FabricaAbstracta<Vehiculo> {
public:
std::unique_ptr<Vehiculo> crearProducto() const override {
return std::make_unique<VehiculoElectrico>();
}
};
class FabricaVehiculosGasolina : public FabricaAbstracta<Vehiculo> {
public:
std::unique_ptr<Vehiculo> crearProducto() const override {
return std::make_unique<VehiculoGasolina>();
}
};
// Función cliente genérica
template <typename Fabrica>
void probarFabrica(const Fabrica& fabrica) {
auto producto = fabrica.crearProducto();
std::cout << producto->conducir() << std::endl;
}
// Uso
int main() {
std::cout << "=== Abstract Factory con Templates ===" << std::endl;
FabricaVehiculosElectricos fabricaElectrica;
FabricaVehiculosGasolina fabricaGasolina;
probarFabrica(fabricaElectrica); // "Conduciendo vehículo eléctrico silenciosamente"
probarFabrica(fabricaGasolina); // "Conduciendo vehículo a gasolina con motor potente"
return 0;
}
Abstract Factory para diferentes sistemas operativos
#include <iostream>
#include <memory>
#include <string>
// Interfaces para diferentes componentes
class Boton {
public:
virtual ~Boton() = default;
virtual std::string dibujar() const = 0;
virtual void onClick() const = 0;
};
class Ventana {
public:
virtual ~Ventana() = default;
virtual std::string renderizar() const = 0;
};
class Menu {
public:
virtual ~Menu() = default;
virtual std::string mostrar() const = 0;
};
// Implementaciones para Windows
class BotonWindows : public Boton {
public:
std::string dibujar() const override { return "Dibujando botón estilo Windows"; }
void onClick() const override { std::cout << "Click en botón Windows" << std::endl; }
};
class VentanaWindows : public Ventana {
public:
std::string renderizar() const override { return "Renderizando ventana estilo Windows"; }
};
class MenuWindows : public Menu {
public:
std::string mostrar() const override { return "Mostrando menú estilo Windows"; }
};
// Implementaciones para macOS
class BotonMacOS : public Boton {
public:
std::string dibujar() const override { return "Dibujando botón estilo macOS"; }
void onClick() const override { std::cout << "Click en botón macOS" << std::endl; }
};
class VentanaMacOS : public Ventana {
public:
std::string renderizar() const override { return "Renderizando ventana estilo macOS"; }
};
class MenuMacOS : public Menu {
public:
std::string mostrar() const override { return "Mostrando menú estilo macOS"; }
};
// Abstract Factory
class FabricaUI {
public:
virtual ~FabricaUI() = default;
virtual std::unique_ptr<Boton> crearBoton() const = 0;
virtual std::unique_ptr<Ventana> crearVentana() const = 0;
virtual std::unique_ptr<Menu> crearMenu() const = 0;
};
// Concrete Factories
class FabricaUIWindows : public FabricaUI {
public:
std::unique_ptr<Boton> crearBoton() const override {
return std::make_unique<BotonWindows>();
}
std::unique_ptr<Ventana> crearVentana() const override {
return std::make_unique<VentanaWindows>();
}
std::unique_ptr<Menu> crearMenu() const override {
return std::make_unique<MenuWindows>();
}
};
class FabricaUIMacOS : public FabricaUI {
public:
std::unique_ptr<Boton> crearBoton() const override {
return std::make_unique<BotonMacOS>();
}
std::unique_ptr<Ventana> crearVentana() const override {
return std::make_unique<VentanaMacOS>();
}
std::unique_ptr<Menu> crearMenu() const override {
return std::make_unique<MenuMacOS>();
}
};
// Aplicación que usa la fábrica
class Aplicacion {
private:
std::unique_ptr<FabricaUI> fabrica;
public:
explicit Aplicacion(std::unique_ptr<FabricaUI> fab) : fabrica(std::move(fab)) {}
void crearInterfaz() {
auto boton = fabrica->crearBoton();
auto ventana = fabrica->crearVentana();
auto menu = fabrica->crearMenu();
std::cout << boton->dibujar() << std::endl;
std::cout << ventana->renderizar() << std::endl;
std::cout << menu->mostrar() << std::endl;
boton->onClick();
}
};
// Uso
int main() {
std::cout << "=== Abstract Factory para UI multiplataforma ===" << std::endl;
// Crear aplicación para Windows
auto appWindows = Aplicacion(std::make_unique<FabricaUIWindows>());
std::cout << "Aplicación Windows:" << std::endl;
appWindows.crearInterfaz();
std::cout << std::endl;
// Crear aplicación para macOS
auto appMacOS = Aplicacion(std::make_unique<FabricaUIMacOS>());
std::cout << "Aplicación macOS:" << std::endl;
appMacOS.crearInterfaz();
return 0;
}
Paso 5: Builder - Construcción paso a paso
El patrón Builder permite construir objetos complejos paso a paso. En C++, este patrón es especialmente útil para construir objetos con muchos parámetros opcionales o configuración compleja, aprovechando las características del lenguaje como RAII, smart pointers y method chaining.
Implementación básica con Builder pattern
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// Producto complejo
class Vehiculo {
private:
std::string motor;
std::string carroceria;
std::string llantas;
std::vector<std::string> accesorios;
public:
Vehiculo(std::string mot, std::string carr, std::string llant)
: motor(std::move(mot)), carroceria(std::move(carr)), llantas(std::move(llant)) {}
void agregarAccesorio(const std::string& accesorio) {
accesorios.push_back(accesorio);
}
void mostrarEspecificaciones() const {
std::cout << "Vehículo con:" << std::endl;
std::cout << " Motor: " << motor << std::endl;
std::cout << " Carrocería: " << carroceria << std::endl;
std::cout << " Llantas: " << llantas << std::endl;
if (!accesorios.empty()) {
std::cout << " Accesorios:" << std::endl;
for (const auto& accesorio : accesorios) {
std::cout << " - " << accesorio << std::endl;
}
}
}
};
// Builder abstracto
class ConstructorVehiculo {
protected:
std::unique_ptr<Vehiculo> vehiculo;
public:
virtual ~ConstructorVehiculo() = default;
virtual void construirMotor() = 0;
virtual void construirCarroceria() = 0;
virtual void construirLlantas() = 0;
std::unique_ptr<Vehiculo> obtenerVehiculo() {
return std::move(vehiculo);
}
protected:
void reset() {
vehiculo = std::make_unique<Vehiculo>("", "", "");
}
};
// Builder concreto para auto deportivo
class ConstructorAutoDeportivo : public ConstructorVehiculo {
public:
ConstructorAutoDeportivo() { reset(); }
void construirMotor() override {
vehiculo->agregarAccesorio("Motor V8 deportivo");
}
void construirCarroceria() override {
vehiculo->agregarAccesorio("Carrocería aerodinámica");
}
void construirLlantas() override {
vehiculo->agregarAccesorio("Llantas de alto rendimiento");
}
};
// Builder concreto para auto familiar
class ConstructorAutoFamiliar : public ConstructorVehiculo {
public:
ConstructorAutoFamiliar() { reset(); }
void construirMotor() override {
vehiculo->agregarAccesorio("Motor V4 eficiente");
}
void construirCarroceria() override {
vehiculo->agregarAccesorio("Carrocería espaciosa");
}
void construirLlantas() override {
vehiculo->agregarAccesorio("Llantas todo terreno");
}
};
// Director que controla el proceso de construcción
class Director {
private:
ConstructorVehiculo* constructor;
public:
void setConstructor(ConstructorVehiculo* constr) {
constructor = constr;
}
std::unique_ptr<Vehiculo> construirVehiculoCompleto() {
if (!constructor) return nullptr;
constructor->construirMotor();
constructor->construirCarroceria();
constructor->construirLlantas();
return constructor->obtenerVehiculo();
}
};
// Uso
int main() {
Director director;
// Construir auto deportivo
auto constructorDeportivo = std::make_unique<ConstructorAutoDeportivo>();
director.setConstructor(constructorDeportivo.get());
auto autoDeportivo = director.construirVehiculoCompleto();
std::cout << "Auto Deportivo:" << std::endl;
autoDeportivo->mostrarEspecificaciones();
// Construir auto familiar
auto constructorFamiliar = std::make_unique<ConstructorAutoFamiliar>();
director.setConstructor(constructorFamiliar.get());
auto autoFamiliar = director.construirVehiculoCompleto();
std::cout << "\nAuto Familiar:" << std::endl;
autoFamiliar->mostrarEspecificaciones();
return 0;
}
Builder fluido con method chaining
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// Producto: Pizza
class Pizza {
private:
std::string masa;
std::string salsa;
std::string queso;
std::vector<std::string> ingredientes;
bool cortezaRellena = false;
public:
Pizza(std::string m, std::string s, std::string q)
: masa(std::move(m)), salsa(std::move(s)), queso(std::move(q)) {}
void agregarIngrediente(const std::string& ingrediente) {
ingredientes.push_back(ingrediente);
}
void setCortezaRellena(bool rellena) {
cortezaRellena = rellena;
}
void mostrarDetalles() const {
std::cout << "Pizza con:" << std::endl;
std::cout << " Masa: " << masa << std::endl;
std::cout << " Salsa: " << salsa << std::endl;
std::cout << " Queso: " << queso << std::endl;
std::cout << " Ingredientes:" << std::endl;
for (const auto& ingrediente : ingredientes) {
std::cout << " - " << ingrediente << std::endl;
}
std::cout << " Corteza rellena: " << (cortezaRellena ? "Sí" : "No") << std::endl;
}
};
// Builder fluido
class ConstructorPizza {
private:
std::string masa;
std::string salsa;
std::string queso;
std::vector<std::string> ingredientes;
bool cortezaRellena = false;
public:
// Métodos que retornan *this para method chaining
ConstructorPizza& conMasa(const std::string& tipoMasa) {
masa = tipoMasa;
return *this;
}
ConstructorPizza& conSalsa(const std::string& tipoSalsa) {
salsa = tipoSalsa;
return *this;
}
ConstructorPizza& conQueso(const std::string& tipoQueso) {
queso = tipoQueso;
return *this;
}
ConstructorPizza& agregarIngrediente(const std::string& ingrediente) {
ingredientes.push_back(ingrediente);
return *this;
}
ConstructorPizza& conCortezaRellena(bool rellena = true) {
cortezaRellena = rellena;
return *this;
}
// Método de construcción
std::unique_ptr<Pizza> construir() const {
auto pizza = std::make_unique<Pizza>(masa, salsa, queso);
for (const auto& ingrediente : ingredientes) {
pizza->agregarIngrediente(ingrediente);
}
pizza->setCortezaRellena(cortezaRellena);
return pizza;
}
};
// Uso del Builder fluido
int main() {
std::cout << "=== Builder Fluido ===" << std::endl;
auto pizza = ConstructorPizza()
.conMasa("delgada")
.conSalsa("tomate")
.conQueso("mozzarella")
.agregarIngrediente("pepperoni")
.agregarIngrediente("champiñones")
.agregarIngrediente("aceitunas")
.conCortezaRellena(true)
.construir();
pizza->mostrarDetalles();
return 0;
}
Builder con parámetros nombrados usando struct
#include <iostream>
#include <memory>
#include <string>
#include <optional>
// Producto: Computadora
class Computadora {
private:
std::string cpu;
std::string gpu;
int ramGB;
int almacenamientoGB;
std::optional<std::string> refrigeracion;
bool wifi;
std::string os;
public:
Computadora(std::string c, std::string g, int r, int a, std::string o)
: cpu(std::move(c)), gpu(std::move(g)), ramGB(r), almacenamientoGB(a), os(std::move(o)) {}
void setRefrigeracion(const std::string& refri) {
refrigeracion = refri;
}
void setWifi(bool tieneWifi) {
wifi = tieneWifi;
}
void mostrarConfiguracion() const {
std::cout << "Computadora:" << std::endl;
std::cout << " CPU: " << cpu << std::endl;
std::cout << " GPU: " << gpu << std::endl;
std::cout << " RAM: " << ramGB << "GB" << std::endl;
std::cout << " Almacenamiento: " << almacenamientoGB << "GB" << std::endl;
if (refrigeracion) {
std::cout << " Refrigeración: " << *refrigeracion << std::endl;
}
std::cout << " WiFi: " << (wifi ? "Sí" : "No") << std::endl;
std::cout << " OS: " << os << std::endl;
}
};
// Builder usando struct para parámetros nombrados
struct ConfigComputadora {
std::string cpu = "Intel i5";
std::string gpu = "Integrada";
int ramGB = 8;
int almacenamientoGB = 256;
std::optional<std::string> refrigeracion = std::nullopt;
bool wifi = true;
std::string os = "Windows 11";
};
class ConstructorComputadora {
private:
ConfigComputadora config;
public:
// Métodos para configurar cada parámetro
ConstructorComputadora& setCPU(const std::string& cpu) {
config.cpu = cpu;
return *this;
}
ConstructorComputadora& setGPU(const std::string& gpu) {
config.gpu = gpu;
return *this;
}
ConstructorComputadora& setRAM(int ramGB) {
config.ramGB = ramGB;
return *this;
}
ConstructorComputadora& setAlmacenamiento(int almacenamientoGB) {
config.almacenamientoGB = almacenamientoGB;
return *this;
}
ConstructorComputadora& setRefrigeracion(const std::string& refrigeracion) {
config.refrigeracion = refrigeracion;
return *this;
}
ConstructorComputadora& setWifi(bool wifi) {
config.wifi = wifi;
return *this;
}
ConstructorComputadora& setOS(const std::string& os) {
config.os = os;
return *this;
}
// Reset para reutilizar el builder
void reset() {
config = ConfigComputadora{};
}
// Método de construcción
std::unique_ptr<Computadora> construir() const {
auto computadora = std::make_unique<Computadora>(
config.cpu, config.gpu, config.ramGB, config.almacenamientoGB, config.os
);
if (config.refrigeracion) {
computadora->setRefrigeracion(*config.refrigeracion);
}
computadora->setWifi(config.wifi);
return computadora;
}
};
// Uso
int main() {
std::cout << "=== Builder con Parámetros Nombrados ===" << std::endl;
ConstructorComputadora constructor;
// Computadora básica
auto pcBasica = constructor
.setCPU("AMD Ryzen 5")
.setRAM(16)
.setAlmacenamiento(512)
.construir();
std::cout << "PC Básica:" << std::endl;
pcBasica->mostrarConfiguracion();
// Computadora gaming
auto pcGaming = constructor
.reset() // Reutilizar el builder
.setCPU("Intel i7")
.setGPU("NVIDIA RTX 4070")
.setRAM(32)
.setAlmacenamiento(1000)
.setRefrigeracion("Líquida")
.setOS("Windows 11")
.construir();
std::cout << "\nPC Gaming:" << std::endl;
pcGaming->mostrarConfiguracion();
return 0;
}
Paso 6: Prototype - Clonación de objetos
El patrón Prototype permite copiar objetos existentes sin depender de sus clases concretas. En C++, este patrón es especialmente útil para objetos costosos de crear o cuando necesitas crear múltiples instancias similares, aprovechando las características del lenguaje como smart pointers y copy constructors.
Implementación básica con copy constructor
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// Prototipo abstracto
class Prototipo {
public:
virtual ~Prototipo() = default;
virtual std::unique_ptr<Prototipo> clonar() const = 0;
virtual void mostrarInfo() const = 0;
};
// Documento concreto
class Documento : public Prototipo {
private:
std::string titulo;
std::string contenido;
std::string autor;
std::string fechaCreacion;
public:
Documento(const std::string& tit, const std::string& cont, const std::string& aut)
: titulo(tit), contenido(cont), autor(aut), fechaCreacion("2025-01-01") {}
// Constructor de copia
Documento(const Documento& otro)
: titulo(otro.titulo), contenido(otro.contenido), autor(otro.autor), fechaCreacion(otro.fechaCreacion) {}
// Clonación superficial
std::unique_ptr<Prototipo> clonar() const override {
return std::make_unique<Documento>(*this);
}
void mostrarInfo() const override {
std::cout << "Documento: " << titulo << " por " << autor << std::endl;
}
// Métodos para modificar el documento
void setTitulo(const std::string& nuevoTitulo) {
titulo = nuevoTitulo;
}
void setContenido(const std::string& nuevoContenido) {
contenido = nuevoContenido;
}
std::string getTitulo() const { return titulo; }
std::string getContenido() const { return contenido; }
};
// Uso
int main() {
std::cout << "=== Prototype con Copy Constructor ===" << std::endl;
auto documentoOriginal = std::make_unique<Documento>("Tutorial C++", "Contenido del tutorial", "José");
std::cout << "Documento original:" << std::endl;
documentoOriginal->mostrarInfo();
// Clonación
auto documentoClonado = documentoOriginal->clonar();
documentoClonado->setTitulo("Tutorial Avanzado C++");
std::cout << "Documento clonado:" << std::endl;
documentoClonado->mostrarInfo();
return 0;
}
Prototype con clonación profunda
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
// Metadata para clonación profunda
struct Metadata {
std::string autor;
double version;
std::vector<std::string> tags;
Metadata(const std::string& aut, double ver, const std::vector<std::string>& tgs)
: autor(aut), version(ver), tags(tgs) {}
};
// Documento complejo con objetos anidados
class DocumentoComplejo : public Prototipo {
private:
std::string titulo;
std::unique_ptr<Metadata> metadata;
public:
DocumentoComplejo(const std::string& tit, const std::string& aut, double ver, const std::vector<std::string>& tgs)
: titulo(tit), metadata(std::make_unique<Metadata>(aut, ver, tgs)) {}
// Constructor de copia para clonación profunda
DocumentoComplejo(const DocumentoComplejo& otro)
: titulo(otro.titulo), metadata(std::make_unique<Metadata>(*otro.metadata)) {}
// Clonación profunda
std::unique_ptr<Prototipo> clonar() const override {
return std::make_unique<DocumentoComplejo>(*this);
}
void mostrarInfo() const override {
std::cout << "Documento: " << titulo << std::endl;
std::cout << " Autor: " << metadata->autor << std::endl;
std::cout << " Versión: " << metadata->version << std::endl;
std::cout << " Tags: ";
for (const auto& tag : metadata->tags) {
std::cout << tag << " ";
}
std::cout << std::endl;
}
// Métodos para modificar
void setVersion(double nuevaVersion) {
metadata->version = nuevaVersion;
}
void agregarTag(const std::string& tag) {
metadata->tags.push_back(tag);
}
std::string getTitulo() const { return titulo; }
};
// Uso de clonación profunda
int main() {
std::cout << "=== Prototype con Clonación Profunda ===" << std::endl;
std::vector<std::string> tags = {"cpp", "tutorial"};
auto docOriginal = std::make_unique<DocumentoComplejo>("Tutorial Complejo", "José", 1.0, tags);
std::cout << "Documento original:" << std::endl;
docOriginal->mostrarInfo();
// Clonación profunda
auto docClonado = std::unique_ptr<DocumentoComplejo>(static_cast<DocumentoComplejo*>(docOriginal->clonar().release()));
// Modificar el clonado
docClonado->setVersion(2.0);
docClonado->agregarTag("avanzado");
std::cout << "Documento clonado modificado:" << std::endl;
docClonado->mostrarInfo();
std::cout << "Documento original (sin cambios):" << std::endl;
docOriginal->mostrarInfo();
return 0;
}
Prototype Manager/Registro
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <functional>
// Prototype Manager
class RegistroPrototipos {
private:
std::unordered_map<std::string, std::unique_ptr<Prototipo>> prototipos;
public:
~RegistroPrototipos() = default;
// Agregar prototipo al registro
void agregarPrototipo(const std::string& nombre, std::unique_ptr<Prototipo> prototipo) {
prototipos[nombre] = std::move(prototipo);
}
// Obtener clon del prototipo
std::unique_ptr<Prototipo> obtenerPrototipo(const std::string& nombre) const {
auto it = prototipos.find(nombre);
if (it != prototipos.end()) {
return it->second->clonar();
}
throw std::invalid_argument("Prototipo '" + nombre + "' no encontrado");
}
// Listar prototipos disponibles
void listarPrototipos() const {
std::cout << "Prototipos disponibles:" << std::endl;
for (const auto& pair : prototipos) {
std::cout << " - " << pair.first << std::endl;
}
}
};
// Función helper para crear prototipos
template <typename T, typename... Args>
std::unique_ptr<T> crearPrototipo(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
// Uso del Prototype Manager
int main() {
std::cout << "=== Prototype Manager ===" << std::endl;
RegistroPrototipos registro;
// Crear y registrar prototipos base
auto documentoBase = crearPrototipo<Documento>("Documento Base", "Contenido base", "Autor Base");
auto emailBase = crearPrototipo<Documento>("Email Base", "Estimado cliente...", "Sistema");
registro.agregarPrototipo("documento", std::move(documentoBase));
registro.agregarPrototipo("email", std::move(emailBase));
registro.listarPrototipos();
// Crear instancias desde prototipos
auto doc1 = registro.obtenerPrototipo("documento");
auto doc2 = registro.obtenerPrototipo("documento");
auto email1 = registro.obtenerPrototipo("email");
// Modificar las instancias
static_cast<Documento*>(doc1.get())->setTitulo("Mi Documento Personal");
static_cast<Documento*>(doc2.get())->setTitulo("Documento Compartido");
static_cast<Documento*>(email1.get())->setContenido("Estimado cliente, su pedido ha sido procesado...");
std::cout << "\nInstancias creadas:" << std::endl;
doc1->mostrarInfo();
doc2->mostrarInfo();
email1->mostrarInfo();
return 0;
}
Prototype con serialización (avanzado)
#include <iostream>
#include <memory>
#include <string>
#include <sstream>
#include <vector>
// Simulación simple de serialización
class Serializable {
public:
virtual ~Serializable() = default;
virtual std::string serializar() const = 0;
virtual void deserializar(const std::string& data) = 0;
};
// Forma geométrica que soporta clonación por serialización
class Forma : public Prototipo, public Serializable {
private:
std::string tipo;
int x, y;
int ancho, alto;
std::string color;
public:
Forma(const std::string& t, int x_pos, int y_pos, int w, int h, const std::string& c)
: tipo(t), x(x_pos), y(y_pos), ancho(w), alto(h), color(c) {}
// Constructor de copia
Forma(const Forma& otra)
: tipo(otra.tipo), x(otra.x), y(otra.y), ancho(otra.ancho), alto(otra.alto), color(otra.color) {}
// Clonación
std::unique_ptr<Prototipo> clonar() const override {
return std::make_unique<Forma>(*this);
}
void mostrarInfo() const override {
std::cout << tipo << " en (" << x << ", " << y << ") tamaño " << ancho << "x" << alto
<< " color " << color << std::endl;
}
// Serialización simple
std::string serializar() const override {
std::stringstream ss;
ss << tipo << "," << x << "," << y << "," << ancho << "," << alto << "," << color;
return ss.str();
}
void deserializar(const std::string& data) override {
std::stringstream ss(data);
std::string token;
std::getline(ss, tipo, ',');
std::getline(ss, token, ','); x = std::stoi(token);
std::getline(ss, token, ','); y = std::stoi(token);
std::getline(ss, token, ','); ancho = std::stoi(token);
std::getline(ss, token, ','); alto = std::stoi(token);
std::getline(ss, color, ',');
}
// Clonación por serialización (útil para objetos muy complejos)
static std::unique_ptr<Forma> clonarPorSerializacion(const Forma& original) {
std::string data = original.serializar();
auto clon = std::make_unique<Forma>("", 0, 0, 0, 0, "");
clon->deserializar(data);
return clon;
}
};
// Uso
int main() {
std::cout << "=== Prototype con Serialización ===" << std::endl;
auto rectangulo = std::make_unique<Forma>("Rectángulo", 10, 20, 100, 50, "azul");
auto circulo = std::make_unique<Forma>("Círculo", 50, 50, 30, 30, "rojo");
std::cout << "Formas originales:" << std::endl;
rectangulo->mostrarInfo();
circulo->mostrarInfo();
// Clonación normal
auto rectClonado = rectangulo->clonar();
auto circClonado = circulo->clonar();
// Clonación por serialización
auto rectSerializado = Forma::clonarPorSerializacion(*rectangulo);
std::cout << "\nClones:" << std::endl;
rectClonado->mostrarInfo();
circClonado->mostrarInfo();
rectSerializado->mostrarInfo();
return 0;
}
Paso 7: Comparación y selección de patrones
Cuándo usar cada patrón en C++
| Patrón | Cuándo usarlo | Ventajas | Desventajas |
|---|---|---|---|
| Singleton | Recursos compartidos, configuración global, conexiones DB | Thread-safe con C++11, RAII automático | Acoplamiento fuerte, difícil de testear |
| Factory Method | Creación polimórfica, plugins, extensibilidad | Type safety, performance optimizado | Puede ser overkill para casos simples |
| Abstract Factory | Familias de objetos relacionados, UI multiplataforma | Type safety fuerte, templates | Complejo de implementar |
| Builder | Objetos complejos, configuración opcional, fluent interface | Method chaining, RAII | Verbose para objetos simples |
| Prototype | Objetos costosos de crear, clonación profunda | Copy semantics de C++, eficiente | Requiere copy constructor correcto |
Consideraciones específicas de C++
Singleton en C++
// ✅ Recomendado: Meyer Singleton (C++11)
class Singleton {
private:
Singleton() = default;
~Singleton() = default;
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static Singleton instance; // Thread-safe en C++11
return instance;
}
};
// ❌ Evitar: Singleton con punteros crudos
class BadSingleton {
private:
static BadSingleton* instance;
static std::mutex mutex;
public:
static BadSingleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (!instance) {
instance = new BadSingleton();
}
return instance;
}
// ¡Fuga de memoria!
};
Factory Method con templates
// ✅ Recomendado: Factory con templates para type safety
template <typename Base, typename Derived, typename... Args>
std::unique_ptr<Base> createInstance(Args&&... args) {
return std::make_unique<Derived>(std::forward<Args>(args)...);
}
// Uso
auto vehiculo = createInstance<Vehiculo, Camion>(/*args*/);
Builder con RAII
// ✅ Recomendado: Builder que aprovecha RAII
class DatabaseBuilder {
private:
std::string host = "localhost";
int port = 5432;
std::string database = "default";
std::optional<std::string> username;
std::optional<std::string> password;
public:
DatabaseBuilder& setHost(const std::string& h) {
host = h;
return *this;
}
DatabaseBuilder& setPort(int p) {
port = p;
return *this;
}
DatabaseBuilder& setCredentials(const std::string& user, const std::string& pass) {
username = user;
password = pass;
return *this;
}
std::unique_ptr<DatabaseConnection> build() {
// Validaciones y construcción con RAII
return std::make_unique<DatabaseConnection>(host, port, database, username, password);
}
};
Antipatrones a evitar en C++
- Singletonitis: No todo necesita ser singleton
- Factory explosion: No crear factories para todo
- Builder for simple objects: Over-engineering
- Prototype sin copy constructor: Olvidar implementar copy semantics
- Memory leaks: No usar smart pointers en factories
- Thread safety: No considerar concurrencia en singletons
Paso 8: Proyecto práctico - Sistema de configuración
Vamos a crear un sistema de configuración que utilice múltiples patrones creacionales para demostrar su uso en un escenario real en C++, aprovechando las características del lenguaje como smart pointers, RAII y templates.
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <nlohmann/json.hpp> // Para serialización JSON
#include <vector>
// Singleton para configuración global
class ConfiguracionGlobal {
private:
static std::unique_ptr<ConfiguracionGlobal> instance;
static std::mutex mutex;
nlohmann::json config;
ConfiguracionGlobal() {
cargarConfiguracionDefault();
}
void cargarConfiguracionDefault() {
config = {
{"database", {
{"host", "localhost"},
{"port", 5432},
{"name", "app_db"}
}},
{"logging", {
{"level", "INFO"},
{"file", "app.log"}
}},
{"features", {
{"cache", true},
{"notifications", false}
}}
};
}
public:
ConfiguracionGlobal(const ConfiguracionGlobal&) = delete;
ConfiguracionGlobal& operator=(const ConfiguracionGlobal&) = delete;
static ConfiguracionGlobal* getInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (!instance) {
instance = std::unique_ptr<ConfiguracionGlobal>(new ConfiguracionGlobal());
}
return instance.get();
}
nlohmann::json obtenerConfig(const std::string& seccion = "") const {
if (seccion.empty()) {
return config;
}
return config.value(seccion, nlohmann::json::object());
}
void actualizarConfig(const std::string& seccion, const std::string& clave, const nlohmann::json& valor) {
std::lock_guard<std::mutex> lock(mutex);
if (!config.contains(seccion)) {
config[seccion] = nlohmann::json::object();
}
config[seccion][clave] = valor;
}
std::string serializar() const {
return config.dump(2);
}
};
// Inicializar los miembros estáticos
std::unique_ptr<ConfiguracionGlobal> ConfiguracionGlobal::instance;
std::mutex ConfiguracionGlobal::mutex;
// Prototype para perfiles de configuración
class PerfilConfiguracion {
private:
std::string nombre;
nlohmann::json config;
public:
PerfilConfiguracion(const std::string& nom, const nlohmann::json& configBase)
: nombre(nom), config(nlohmann::json::parse(configBase.dump())) {}
std::unique_ptr<PerfilConfiguracion> clonar() const {
return std::make_unique<PerfilConfiguracion>(nombre + "_clon", config);
}
void personalizar(const std::unordered_map<std::string, nlohmann::json>& cambios) {
for (const auto& [clave, valor] : cambios) {
if (clave.find('.') != std::string::npos) {
// Manejar claves anidadas como "database.host"
auto puntoPos = clave.find('.');
std::string seccion = clave.substr(0, puntoPos);
std::string subclave = clave.substr(puntoPos + 1);
if (!config.contains(seccion)) {
config[seccion] = nlohmann::json::object();
}
config[seccion][subclave] = valor;
} else {
config[clave] = valor;
}
}
}
std::string getNombre() const { return nombre; }
nlohmann::json getConfig() const { return config; }
std::string serializar() const {
nlohmann::json perfil = {
{"nombre", nombre},
{"config", config}
};
return perfil.dump(2);
}
};
// Factory para crear perfiles
class FabricaPerfiles {
private:
ConfiguracionGlobal* configGlobal;
public:
FabricaPerfiles() : configGlobal(ConfiguracionGlobal::getInstance()) {}
std::unique_ptr<PerfilConfiguracion> crearPerfilDesarrollo() const {
auto perfil = std::make_unique<PerfilConfiguracion>("desarrollo", configGlobal->obtenerConfig());
perfil->personalizar({
{"database.host", "dev-db.local"},
{"database.name", "dev_app_db"},
{"logging.level", "DEBUG"},
{"features.cache", false}
});
return perfil;
}
std::unique_ptr<PerfilConfiguracion> crearPerfilProduccion() const {
auto perfil = std::make_unique<PerfilConfiguracion>("produccion", configGlobal->obtenerConfig());
perfil->personalizar({
{"database.host", "prod-db.company.com"},
{"database.port", 5433},
{"logging.level", "WARNING"},
{"features.notifications", true}
});
return perfil;
}
std::unique_ptr<PerfilConfiguracion> crearPerfilTesting() const {
auto perfil = std::make_unique<PerfilConfiguracion>("testing", configGlobal->obtenerConfig());
perfil->personalizar({
{"database.host", "test-db.local"},
{"database.name", "test_app_db"},
{"logging.level", "DEBUG"},
{"features.cache", false}
});
return perfil;
}
};
// Builder para configuración personalizada
class ConstructorConfiguracionPersonalizada {
private:
std::unique_ptr<PerfilConfiguracion> perfil;
public:
ConstructorConfiguracionPersonalizada()
: perfil(std::make_unique<PerfilConfiguracion>("personalizado", ConfiguracionGlobal::getInstance()->obtenerConfig())) {}
ConstructorConfiguracionPersonalizada& configurarBaseDatos(const std::string& host, int port = 5432, const std::string& name = "") {
std::unordered_map<std::string, nlohmann::json> cambios = {
{"database.host", host},
{"database.port", port}
};
if (!name.empty()) {
cambios["database.name"] = name;
}
perfil->personalizar(cambios);
return *this;
}
ConstructorConfiguracionPersonalizada& configurarLogging(const std::string& level = "INFO", const std::string& file = "") {
std::unordered_map<std::string, nlohmann::json> cambios = {
{"logging.level", level}
};
if (!file.empty()) {
cambios["logging.file"] = file;
}
perfil->personalizar(cambios);
return *this;
}
ConstructorConfiguracionPersonalizada& habilitarFeatures(const std::vector<std::string>& features) {
std::unordered_map<std::string, nlohmann::json> cambios;
for (const auto& feature : features) {
cambios["features." + feature] = true;
}
perfil->personalizar(cambios);
return *this;
}
ConstructorConfiguracionPersonalizada& deshabilitarFeatures(const std::vector<std::string>& features) {
std::unordered_map<std::string, nlohmann::json> cambios;
for (const auto& feature : features) {
cambios["features." + feature] = false;
}
perfil->personalizar(cambios);
return *this;
}
std::unique_ptr<PerfilConfiguracion> construir() {
return std::move(perfil);
}
};
// Uso del sistema completo
int main() {
std::cout << "=== Sistema de Configuración con Patrones Creacionales ===" << std::endl;
// Singleton - configuración global
ConfiguracionGlobal* configGlobal = ConfiguracionGlobal::getInstance();
std::cout << "Configuración global:" << std::endl;
std::cout << configGlobal->serializar() << std::endl;
// Factory - crear perfiles predefinidos
FabricaPerfiles fabrica;
auto perfilDev = fabrica.crearPerfilDesarrollo();
auto perfilProd = fabrica.crearPerfilProduccion();
std::cout << "\nPerfil Desarrollo:" << std::endl;
std::cout << perfilDev->serializar() << std::endl;
std::cout << "\nPerfil Producción:" << std::endl;
std::cout << perfilProd->serializar() << std::endl;
// Builder - configuración personalizada
auto perfilPersonalizado = ConstructorConfiguracionPersonalizada()
.configurarBaseDatos("mi-servidor.com", 3306, "mi_db")
.configurarLogging("ERROR", "errores.log")
.habilitarFeatures({"cache", "notifications"})
.deshabilitarFeatures({"debug"})
.construir();
std::cout << "\nPerfil Personalizado:" << std::endl;
std::cout << perfilPersonalizado->serializar() << std::endl;
// Prototype - clonar y modificar perfiles
auto perfilClonado = perfilDev->clonar();
perfilClonado->personalizar({
{"database.port", 3307}
});
std::cout << "\nPerfil Clonado Modificado:" << std::endl;
std::cout << perfilClonado->serializar() << std::endl;
return 0;
}
Explicación del proyecto
Este sistema de configuración demuestra la combinación de múltiples patrones creacionales:
- Singleton:
ConfiguracionGlobalgarantiza una única instancia de configuración - Prototype:
PerfilConfiguracionpermite clonar configuraciones existentes - Factory:
FabricaPerfilescrea diferentes tipos de perfiles preconfigurados - Builder:
ConstructorConfiguracionPersonalizadaconstruye configuraciones complejas paso a paso
Ventajas de la implementación en C++
- Thread Safety: El singleton es thread-safe con mutex
- RAII: Los smart pointers manejan automáticamente la memoria
- Type Safety: El compilador verifica tipos en tiempo de compilación
- Performance: Zero-cost abstractions y optimizaciones del compilador
- Exception Safety: Los smart pointers garantizan no hay memory leaks
Paso 9: Antipatrones y errores comunes
Errores comunes en patrones creacionales en C++
- Singletonitis: Usar Singleton para todo
- Factory explosion: Crear factories innecesarias
- Builder overkill: Usar Builder para objetos simples
- Prototype sin copy semantics: Olvidar implementar copy constructor
- Memory leaks: No usar smart pointers en factories
- Thread safety: No considerar concurrencia en singletons
Cómo evitarlos
// ❌ Antipatrón: Singleton para todo
class TodoEsSingleton {
private:
static TodoEsSingleton* instance;
static std::mutex mutex;
public:
static TodoEsSingleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (!instance) {
instance = new TodoEsSingleton(); // ¡Posible memory leak!
}
return instance;
}
// Funciones que no deberían estar en un singleton
void hacerAlgo() { /* ... */ }
void hacerOtraCosa() { /* ... */ }
};
// ✅ Mejor: Usar inyección de dependencias
class ServicioUsuario {
private:
std::shared_ptr<RepositorioUsuario> repositorio;
public:
ServicioUsuario(std::shared_ptr<RepositorioUsuario> repo) : repositorio(repo) {}
void registrarUsuario(const std::string& nombre, const std::string& email) {
repositorio->guardar({nombre, email});
}
};
class RepositorioUsuario {
private:
std::shared_ptr<DatabaseConnection> db;
public:
RepositorioUsuario(std::shared_ptr<DatabaseConnection> database) : db(database) {}
void guardar(const Usuario& usuario) {
db->ejecutar("INSERT INTO usuarios...");
}
};
// Configuración centralizada con inyección de dependencias
std::unique_ptr<ServicioUsuario> configurarAplicacion() {
auto db = std::make_shared<DatabaseConnection>();
auto repo = std::make_shared<RepositorioUsuario>(db);
return std::make_unique<ServicioUsuario>(repo);
}
Errores específicos de C++ a evitar
// ❌ Error: Singleton no thread-safe
class BadSingleton {
private:
static BadSingleton* instance; // Sin mutex
public:
static BadSingleton* getInstance() {
if (!instance) { // Race condition!
instance = new BadSingleton();
}
return instance;
}
};
// ✅ Correcto: Thread-safe con C++11
class GoodSingleton {
private:
static std::unique_ptr<GoodSingleton> instance;
static std::mutex mutex;
public:
static GoodSingleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (!instance) {
instance = std::unique_ptr<GoodSingleton>(new GoodSingleton());
}
return instance.get();
}
};
// ❌ Error: Factory que retorna punteros crudos
class BadFactory {
public:
static Producto* crearProducto(const std::string& tipo) {
if (tipo == "A") return new ProductoA(); // ¡Memory leak!
if (tipo == "B") return new ProductoB(); // ¡Memory leak!
return nullptr;
}
};
// ✅ Correcto: Factory con smart pointers
class GoodFactory {
public:
static std::unique_ptr<Producto> crearProducto(const std::string& tipo) {
if (tipo == "A") return std::make_unique<ProductoA>();
if (tipo == "B") return std::make_unique<ProductoB>();
throw std::invalid_argument("Tipo no válido");
}
};
// ❌ Error: Prototype sin copy constructor correcto
class BadPrototype {
private:
std::unique_ptr<Recurso> recurso; // No se copia correctamente
public:
BadPrototype(const BadPrototype& otro) {
// Copia superficial - ¡el recurso se comparte!
recurso = otro.recurso;
}
};
// ✅ Correcto: Prototype con clonación profunda
class GoodPrototype {
private:
std::unique_ptr<Recurso> recurso;
public:
GoodPrototype(const GoodPrototype& otro) {
recurso = std::make_unique<Recurso>(*otro.recurso); // Clonación profunda
}
std::unique_ptr<GoodPrototype> clonar() const {
return std::make_unique<GoodPrototype>(*this);
}
};
Cuándo NO usar patrones creacionales
// ❌ Over-engineering: Builder para objeto simple
class Punto {
public:
int x, y;
// No necesita Builder
Punto(int x, int y) : x(x), y(y) {}
};
// ✅ Simple y directo
auto punto = Punto(10, 20);
// ❌ Over-engineering: Factory para objeto sin lógica de creación
class Configuracion {
public:
std::string host;
int puerto;
// No necesita Factory
Configuracion(const std::string& h, int p) : host(h), puerto(p) {}
};
// ✅ Simple y directo
auto config = Configuracion("localhost", 8080);
// ❌ Over-engineering: Singleton para objeto sin estado global
class UtilidadesMatematicas {
public:
double calcularAreaCirculo(double radio) {
return 3.14159 * radio * radio;
}
// No necesita ser Singleton
};
// ✅ Simple función estática o namespace
namespace UtilidadesMatematicas {
double calcularAreaCirculo(double radio) {
return 3.14159 * radio * radio;
}
}
Reglas generales para C++
- Usa smart pointers: Siempre retorna
std::unique_ptrostd::shared_ptrde factories - Implementa copy semantics: Para Prototype, implementa correctamente copy constructor y copy assignment
- Considera thread safety: Los singletons deben ser thread-safe
- Usa RAII: Los builders deben aprovechar RAII para gestión de recursos
- Evita memory leaks: Nunca uses
newsin smart pointers en código moderno - Prefiere templates: Para factories genéricos, considera usar templates
- Mantén simple: No uses patrones complejos para problemas simples
Conclusión
¡Has dominado los patrones de diseño creacionales en C++! Estos patrones te permiten crear objetos de manera flexible y mantenible, aprovechando las fortalezas únicas del lenguaje como type safety, RAII, smart pointers y zero-cost abstractions.
Beneficios de aplicar patrones creacionales en C++
- Type Safety: El sistema de tipos fuerte previene errores en tiempo de compilación
- Performance: Zero-cost abstractions y optimizaciones del compilador
- Memory Safety: RAII y smart pointers eliminan memory leaks
- Thread Safety: Patrones como Singleton pueden ser thread-safe desde el diseño
- Modern C++: Integración con C++11/14/17/20 features
- Templates: Programación genérica para patrones reutilizables
- Exception Safety: Smart pointers garantizan manejo correcto de excepciones
Cuándo aplicar cada patrón en C++
| Escenario | Patrón Recomendado | Razón |
|---|---|---|
| Recursos compartidos | Singleton | Thread-safe con C++11, RAII automático |
| Creación polimórfica | Factory Method | Type safety, performance optimizado |
| Familias de objetos | Abstract Factory | Type safety fuerte, templates |
| Configuración compleja | Builder | Method chaining, RAII |
| Objetos costosos | Prototype | Copy semantics eficientes |
Practica implementando estos patrones en tus proyectos y combina diferentes patrones según las necesidades específicas de tu aplicación. Recuerda que en C++, la elección del patrón correcto puede tener un impacto significativo en el performance y la seguridad de memoria.
Para más tutoriales sobre patrones de diseño y C++ moderno, visita nuestra sección de tutoriales.
¡Sigue practicando y aplicando estos patrones en proyectos reales de C++!
💡 Tip Importante
📝 Mejores Prácticas en Patrones Creacionales con C++
- Usa smart pointers: Siempre retorna
std::unique_ptrostd::shared_ptrde factories- Implementa copy semantics: Para Prototype, implementa correctamente copy constructor
- Considera thread safety: Los singletons deben ser thread-safe en C++
- Aprovecha RAII: Los builders deben usar RAII para gestión de recursos
- Evita memory leaks: Nunca uses
newsin smart pointers en código moderno- Prefiere templates: Para factories genéricos, considera usar templates
- Usa C++11 features:
std::call_once,std::unique_ptr, lambdas- Documenta ownership: Especifica quién es dueño de los objetos creados
- Considera performance: Evalúa el overhead de cada patrón en tu contexto
- Usa conceptos modernos: C++20 concepts para constraints de templates
📚 Recursos Recomendados para C++:
- Design Patterns in Modern C++ - Dmitri Nesteruk
- Modern C++ Design - Andrei Alexandrescu
- C++ Core Guidelines - Bjarne Stroustrup y Herb Sutter
- Effective Modern C++ - Scott Meyers
- Refactoring.Guru - Creational Patterns - Ejemplos en C++
¡Estos patrones y recursos te ayudarán a escribir C++ moderno, seguro y eficiente!
No hay comentarios aún
Sé el primero en comentar este tutorial.