Imagen destacada del tutorial: Patrones de Diseño Creacionales en C++
Patrones de Diseño

Patrones de Diseño Creacionales en C++

José Elías Romero Guanipa
03 Sep 2025

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.

patrones diseño patrones creacionales singleton factory builder +6 más

¡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?

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: AppConfig con lazy initialization
  • Conexión a base de datos: DatabaseConnection con RAII
  • Sistema de logging: Logger con thread safety
  • Gestor de caché: CacheManager con smart pointers
  • Pool de conexiones: ConnectionPool con 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:

  1. Singleton: ConfiguracionGlobal garantiza una única instancia de configuración
  2. Prototype: PerfilConfiguracion permite clonar configuraciones existentes
  3. Factory: FabricaPerfiles crea diferentes tipos de perfiles preconfigurados
  4. Builder: ConstructorConfiguracionPersonalizada construye 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++

  1. Singletonitis: Usar Singleton para todo
  2. Factory explosion: Crear factories innecesarias
  3. Builder overkill: Usar Builder para objetos simples
  4. Prototype sin copy semantics: Olvidar implementar copy constructor
  5. Memory leaks: No usar smart pointers en factories
  6. 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++

  1. Usa smart pointers: Siempre retorna std::unique_ptr o std::shared_ptr de factories
  2. Implementa copy semantics: Para Prototype, implementa correctamente copy constructor y copy assignment
  3. Considera thread safety: Los singletons deben ser thread-safe
  4. Usa RAII: Los builders deben aprovechar RAII para gestión de recursos
  5. Evita memory leaks: Nunca uses new sin smart pointers en código moderno
  6. Prefiere templates: Para factories genéricos, considera usar templates
  7. 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_ptr o std::shared_ptr de 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 new sin 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++:

¡Estos patrones y recursos te ayudarán a escribir C++ moderno, seguro y eficiente!

Toca los botones para interactuar

Comentarios

Comentarios

Inicia sesión para dejar un comentario.

No hay comentarios aún

Sé el primero en comentar este tutorial.

Tutoriales Relacionados

Descubre más tutoriales relacionados que podrían ser de tu interés

Imagen destacada del tutorial relacionado: Principios SOLID de Diseño de Software en C++
Patrones de Diseño

Principios SOLID de Diseño de Software en C++

Aprende los principios SOLID de diseño de software (SRP, OCP, LSP, ISP, DIP) con ejemplos prácticos en C++ y mejora la calidad de tu código con type safety y performance.

José Elías Romero Guanipa
01 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño de Comportamiento en C++
Patrones de Diseño

Patrones de Diseño de Comportamiento en C++

Aprende patrones de diseño de comportamiento como Observer, Strategy, Command, State, Template Method, Iterator, Mediator y Memento con ejemplos prácticos en C++. Descubre cómo implementar estos patrones aprovechando las fortalezas del lenguaje C++ como type safety, performance y memory management.

José Elías Romero Guanipa
02 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño Estructurales en C++
Patrones de Diseño

Patrones de Diseño Estructurales en C++

Aprende patrones de diseño estructurales como Adapter, Decorator, Facade y Proxy con ejemplos prácticos en C++, aprovechando las fortalezas del lenguaje para estructuras robustas y eficientes.

José Elías Romero Guanipa
04 Sep 2025
Imagen destacada del tutorial relacionado: Patrones de Diseño - Aplicaciones Avanzadas
Patrones de Diseño

Patrones de Diseño - Aplicaciones Avanzadas

Aprende aplicaciones avanzadas de patrones de diseño en arquitecturas empresariales como microservicios, CQRS, event sourcing y más con ejemplos prácticos en C++.

José Elías Romero Guanipa
05 Sep 2025
Foto de perfil del autor José Elías Romero Guanipa
José Elías Romero Guanipa
Autor

🌟 Nube de Etiquetas

Descubre temas populares en nuestros tutoriales

python
python 25 tutoriales
poo
poo 8 tutoriales
ciencia de datos
ciencia de datos 8 tutoriales
patrones diseño
patrones diseño 7 tutoriales
matplotlib
matplotlib 7 tutoriales
pandas
pandas 6 tutoriales
visualizacion
visualizacion 6 tutoriales
principiante
principiante 5 tutoriales
numpy
numpy 5 tutoriales
c++
c++ 5 tutoriales
estadistica
estadistica 4 tutoriales
cpp
cpp 4 tutoriales
bases de datos
bases de datos 4 tutoriales
dataframe
dataframe 4 tutoriales
csv
csv 3 tutoriales
json
json 3 tutoriales
machine learning
machine learning 3 tutoriales
rendimiento
rendimiento 3 tutoriales
mysql
mysql 3 tutoriales
postgresql
postgresql 3 tutoriales
analisis de datos
analisis de datos 3 tutoriales
graficos
graficos 3 tutoriales
excepciones
excepciones 2 tutoriales
algoritmos
algoritmos 2 tutoriales
estructuras datos
estructuras datos 2 tutoriales

Las etiquetas más grandes y brillantes aparecen en más tutoriales

logo logo

©2024 ViveBTC