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

Patrones de Diseño Estructurales en C++

José Elías Romero Guanipa
04 Sep 2025

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.

patrones diseño patrones estructurales adapter decorator facade +8 más

¡Domina los patrones de diseño estructurales en C++! En este tutorial especializado te guiaré paso a paso para que aprendas los patrones fundamentales de estructura, incluyendo Adapter, Decorator, Facade, Proxy, Bridge, Composite y Flyweight, con ejemplos prácticos y casos de uso reales en C++.

Objetivo: Aprender los patrones de diseño estructurales más importantes, sus implementaciones en C++, ventajas, desventajas y cuándo aplicarlos para organizar y simplificar la estructura de tus aplicaciones, aprovechando las fortalezas únicas del lenguaje.

¿Por qué C++ para patrones estructurales?

C++ es especialmente poderoso para patrones estructurales gracias a sus características únicas:

  • Type Safety: El sistema de tipos fuerte de C++ detecta errores de estructura en tiempo de compilación
  • Zero-Cost Abstractions: Los patrones estructurales no tienen overhead de runtime cuando se implementan correctamente
  • Templates: Permiten implementaciones genéricas y type-safe de patrones como Adapter y Bridge
  • RAII: Resource Acquisition Is Initialization asegura gestión segura de recursos en patrones como Proxy y Facade
  • Smart Pointers: std::shared_ptr, std::unique_ptr y std::weak_ptr facilitan la implementación de patrones con ownership claro
  • Multiple Inheritance: Soporte nativo para herencia múltiple, útil en patrones como Decorator
  • STL Integration: Los patrones se integran naturalmente con la Standard Template Library
  • Performance: Implementaciones eficientes con inlining y optimizaciones del compilador

Índice

Paso 1: ¿Qué son los patrones estructurales?

Los patrones de diseño estructurales se centran en cómo las clases y objetos se componen para formar estructuras más grandes. Estos patrones ayudan a asegurar que cuando una parte del sistema cambia, el resto del sistema no se vea afectado, aprovechando las características únicas de C++ para crear estructuras robustas y eficientes.

¿Por qué C++ para patrones estructurales?

  • Type Safety: El sistema de tipos fuerte detecta errores de estructura en compilación
  • Zero-Cost Abstractions: Los patrones no tienen overhead de runtime
  • Templates: Implementaciones genéricas y type-safe
  • RAII: Gestión automática de recursos con smart pointers
  • Multiple Inheritance: Soporte nativo para herencia múltiple
  • STL Integration: Integración natural con contenedores y algoritmos estándar
  • Performance: Implementaciones eficientes con optimizaciones del compilador

Clasificación de patrones estructurales

Patrón Propósito Complejidad Beneficio en C++
Adapter Conectar interfaces incompatibles Media Templates para type-safe adapters
Decorator Añadir funcionalidad dinámicamente Baja Herencia múltiple nativa
Facade Interfaz simplificada Baja RAII para gestión de recursos
Proxy Control de acceso Media Smart pointers para ownership claro
Bridge Separar abstracción e implementación Alta Templates para implementaciones genéricas
Composite Estructuras de árbol Media STL containers para estructuras eficientes
Flyweight Compartir objetos eficientemente Alta std::shared_ptr para sharing inteligente

Paso 2: Adapter - Conectar interfaces incompatibles

El patrón Adapter permite que clases con interfaces incompatibles trabajen juntas, actuando como un puente entre diferentes interfaces sin modificar el código existente. En C++, este patrón es especialmente poderoso gracias a los templates y la herencia múltiple.

Implementación básica (Adapter de objeto)

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

// Sistema existente que queremos adaptar
class LegacyWebService {
public:
    std::unordered_map<std::string, std::string> getLegacyData() {
        return {
            {"nombre_completo", "Ana García"},
            {"edad_persona", "28"},
            {"correo_usuario", "[email protected]"},
            {"telefono_contacto", "+123456789"}
        };
    }
};

// Nueva interfaz que esperamos (Target)
class DataClient {
public:
    virtual ~DataClient() = default;
    virtual std::unordered_map<std::string, std::string> getUserData() = 0;
};

// Adapter que convierte la interfaz antigua a la nueva
class WebServiceAdapter : public DataClient {
public:
    explicit WebServiceAdapter(std::shared_ptr<LegacyWebService> legacy_service)
        : legacy_service_(legacy_service) {}

    std::unordered_map<std::string, std::string> getUserData() override {
        auto legacy_data = legacy_service_->getLegacyData();

        // Transformar los datos al nuevo formato
        return {
            {"nombre", legacy_data["nombre_completo"]},
            {"edad", legacy_data["edad_persona"]},
            {"email", legacy_data["correo_usuario"]},
            {"telefono", legacy_data["telefono_contacto"]}
        };
    }

private:
    std::shared_ptr<LegacyWebService> legacy_service_;
};

// Uso del adapter
int main() {
    auto legacy_service = std::make_shared<LegacyWebService>();
    auto adapter = std::make_shared<WebServiceAdapter>(legacy_service);

    auto user_data = adapter->getUserData();

    for (const auto& [key, value] : user_data) {
        std::cout << key << ": " << value << std::endl;
    }

    return 0;
}

Adapter con templates (type-safe)

#include <iostream>
#include <memory>
#include <string>
#include <vector>

// Target interface genérica
template<typename T>
class DataProcessor {
public:
    virtual ~DataProcessor() = default;
    virtual T processData(const std::string& input) = 0;
};

// Adaptee existente
class LegacyStringProcessor {
public:
    std::vector<std::string> parseString(const std::string& input) {
        std::vector<std::string> result;
        std::string temp;
        for (char c : input) {
            if (c == ',') {
                result.push_back(temp);
                temp.clear();
            } else {
                temp += c;
            }
        }
        if (!temp.empty()) {
            result.push_back(temp);
        }
        return result;
    }
};

// Adapter con template para type safety
template<typename T>
class StringProcessorAdapter : public DataProcessor<T> {
public:
    explicit StringProcessorAdapter(std::shared_ptr<LegacyStringProcessor> legacy)
        : legacy_processor_(legacy) {}

    T processData(const std::string& input) override {
        auto parsed = legacy_processor_->parseString(input);

        if constexpr (std::is_same_v<T, std::vector<std::string>>) {
            return parsed;
        } else if constexpr (std::is_same_v<T, std::string>) {
            std::string result;
            for (size_t i = 0; i < parsed.size(); ++i) {
                result += parsed[i];
                if (i < parsed.size() - 1) result += " ";
            }
            return result;
        }

        throw std::runtime_error("Tipo no soportado");
    }

private:
    std::shared_ptr<LegacyStringProcessor> legacy_processor_;
};

// Uso del adapter con templates
int main() {
    auto legacy_processor = std::make_shared<LegacyStringProcessor>();

    // Adapter para vector de strings
    auto vector_adapter = std::make_shared<StringProcessorAdapter<std::vector<std::string>>>(legacy_processor);
    auto vector_result = vector_adapter->processData("hello,world,test");

    std::cout << "Vector result: ";
    for (const auto& str : vector_result) {
        std::cout << str << " ";
    }
    std::cout << std::endl;

    // Adapter para string único
    auto string_adapter = std::make_shared<StringProcessorAdapter<std::string>>(legacy_processor);
    auto string_result = string_adapter->processData("hello,world,test");

    std::cout << "String result: " << string_result << std::endl;

    return 0;
}

Adapter de clase (herencia múltiple)

#include <iostream>
#include <memory>
#include <string>

// Target interface
class NotificationService {
public:
    virtual ~NotificationService() = default;
    virtual void sendNotification(const std::string& message, const std::string& recipient) = 0;
};

// Adaptee (clase existente incompatible)
class LegacyEmailService {
public:
    void sendEmail(const std::string& recipient, const std::string& subject, const std::string& body) {
        std::cout << "Enviando correo a " << recipient << std::endl;
        std::cout << "Asunto: " << subject << std::endl;
        std::cout << "Cuerpo: " << body << std::endl;
    }
};

// Adapter usando herencia múltiple (posible en C++)
class EmailAdapter : public NotificationService, private LegacyEmailService {
public:
    void sendNotification(const std::string& message, const std::string& recipient) override {
        // Adaptar la interfaz
        sendEmail(recipient, "Notificación del Sistema", message);
    }
};

// Uso del adapter de clase
int main() {
    EmailAdapter adapter;

    adapter.sendNotification("Bienvenido al sistema", "[email protected]");

    return 0;
}

Caso real: Integración con APIs externas

#include <iostream>
#include <memory>
#include <string>
#include <nlohmann/json.hpp> // Para manejo de JSON

// Nuestra interfaz estándar
class PaymentProvider {
public:
    virtual ~PaymentProvider() = default;
    virtual nlohmann::json processPayment(double amount, const std::string& card_token, const std::string& description) = 0;
};

// API externa con interfaz diferente
class ExternalPaymentAPI {
public:
    nlohmann::json charge(int amount_in_cents, const std::string& card_token, const std::string& metadata) {
        // Simular llamada a API externa
        nlohmann::json response = {
            {"status", "success"},
            {"transaction_id", "txn_123456"},
            {"amount", amount_in_cents / 100.0}
        };
        return response;
    }
};

// Adapter para la API externa
class ExternalPaymentAdapter : public PaymentProvider {
public:
    explicit ExternalPaymentAdapter(std::shared_ptr<ExternalPaymentAPI> api)
        : external_api_(api) {}

    nlohmann::json processPayment(double amount, const std::string& card_token, const std::string& description) override {
        // Convertir parámetros al formato de la API externa
        int amount_in_cents = static_cast<int>(amount * 100);
        std::string metadata = "{\"description\":\"" + description + "\"}";

        // Llamar a la API externa
        auto result = external_api_->charge(amount_in_cents, card_token, metadata);

        // Convertir respuesta al formato esperado
        return {
            {"success", result["status"] == "success"},
            {"transaction_id", result["transaction_id"]},
            {"amount", result["amount"]}
        };
    }

private:
    std::shared_ptr<ExternalPaymentAPI> external_api_;
};

// Uso del adapter
int main() {
    auto external_api = std::make_shared<ExternalPaymentAPI>();
    auto payment_processor = std::make_shared<ExternalPaymentAdapter>(external_api);

    auto result = payment_processor->processPayment(99.99, "tok_visa_123", "Compra en tienda");

    std::cout << "Payment result: " << result.dump(2) << std::endl;

    return 0;
}

Ventajas del Adapter en C++

  • Type Safety: Templates permiten adapters type-safe
  • Zero-Cost Abstractions: No hay overhead de runtime
  • RAII: Gestión automática de recursos con smart pointers
  • Multiple Inheritance: Soporte nativo para adapters de clase
  • Exception Safety: Control robusto de errores
  • Performance: Inlining de métodos adaptados

Paso 3: Decorator - Añadir funcionalidad dinámicamente

El patrón Decorator permite añadir responsabilidades adicionales a un objeto de manera dinámica, proporcionando una alternativa flexible a la herencia múltiple para extender funcionalidades. En C++, este patrón aprovecha la herencia múltiple nativa y los templates.

Implementación básica con herencia múltiple

#include <iostream>
#include <memory>
#include <string>

// Componente base
class Beverage {
public:
    virtual ~Beverage() = default;
    virtual double cost() const = 0;
    virtual std::string description() const = 0;
};

// Componente concreto
class Coffee : public Beverage {
public:
    double cost() const override {
        return 2.0;
    }

    std::string description() const override {
        return "Café";
    }
};

// Decorator base
class BeverageDecorator : public Beverage {
public:
    explicit BeverageDecorator(std::unique_ptr<Beverage> beverage)
        : beverage_(std::move(beverage)) {}

    double cost() const override {
        return beverage_->cost();
    }

    std::string description() const override {
        return beverage_->description();
    }

protected:
    std::unique_ptr<Beverage> beverage_;
};

// Decorators concretos usando herencia múltiple
class Milk : public BeverageDecorator {
public:
    explicit Milk(std::unique_ptr<Beverage> beverage)
        : BeverageDecorator(std::move(beverage)) {}

    double cost() const override {
        return BeverageDecorator::cost() + 0.5;
    }

    std::string description() const override {
        return BeverageDecorator::description() + " con leche";
    }
};

class Sugar : public BeverageDecorator {
public:
    explicit Sugar(std::unique_ptr<Beverage> beverage)
        : BeverageDecorator(std::move(beverage)) {}

    double cost() const override {
        return BeverageDecorator::cost() + 0.2;
    }

    std::string description() const override {
        return BeverageDecorator::description() + " con azúcar";
    }
};

class Cinnamon : public BeverageDecorator {
public:
    explicit Cinnamon(std::unique_ptr<Beverage> beverage)
        : BeverageDecorator(std::move(beverage)) {}

    double cost() const override {
        return BeverageDecorator::cost() + 0.3;
    }

    std::string description() const override {
        return BeverageDecorator::description() + " con canela";
    }
};

// Uso
int main() {
    auto beverage = std::make_unique<Coffee>();
    std::cout << beverage->description() << ": $" << beverage->cost() << std::endl;

    beverage = std::make_unique<Milk>(std::move(beverage));
    std::cout << beverage->description() << ": $" << beverage->cost() << std::endl;

    beverage = std::make_unique<Sugar>(std::move(beverage));
    std::cout << beverage->description() << ": $" << beverage->cost() << std::endl;

    beverage = std::make_unique<Cinnamon>(std::move(beverage));
    std::cout << beverage->description() << ": $" << beverage->cost() << std::endl;

    return 0;
}

Decorator con templates (type-safe)

#include <iostream>
#include <memory>
#include <string>
#include <functional>
#include <chrono>

// Componente base genérico
template<typename T>
class StreamProcessor {
public:
    virtual ~StreamProcessor() = default;
    virtual T process(const T& input) = 0;
};

// Componente concreto
class StringProcessor : public StreamProcessor<std::string> {
public:
    std::string process(const std::string& input) override {
        return "Procesado: " + input;
    }
};

// Decorator base con template
template<typename T>
class StreamDecorator : public StreamProcessor<T> {
public:
    explicit StreamDecorator(std::unique_ptr<StreamProcessor<T>> processor)
        : processor_(std::move(processor)) {}

    T process(const T& input) override {
        return processor_->process(input);
    }

protected:
    std::unique_ptr<StreamProcessor<T>> processor_;
};

// Decorators concretos
class UppercaseDecorator : public StreamDecorator<std::string> {
public:
    explicit UppercaseDecorator(std::unique_ptr<StreamProcessor<std::string>> processor)
        : StreamDecorator<std::string>(std::move(processor)) {}

    std::string process(const std::string& input) override {
        std::string result = StreamDecorator::process(input);
        // Convertir a mayúsculas
        for (char& c : result) {
            c = std::toupper(c);
        }
        return result;
    }
};

class PrefixDecorator : public StreamDecorator<std::string> {
public:
    explicit PrefixDecorator(std::unique_ptr<StreamProcessor<std::string>> processor, const std::string& prefix)
        : StreamDecorator<std::string>(std::move(processor)), prefix_(prefix) {}

    std::string process(const std::string& input) override {
        return prefix_ + StreamDecorator::process(input);
    }

private:
    std::string prefix_;
};

// Uso
int main() {
    auto processor = std::make_unique<StringProcessor>();

    processor = std::make_unique<UppercaseDecorator>(std::move(processor));
    processor = std::make_unique<PrefixDecorator>(std::move(processor), ">>> ");

    std::string result = processor->process("hello world");
    std::cout << result << std::endl;

    return 0;
}

Decoradores funcionales modernos

#include <iostream>
#include <functional>
#include <chrono>
#include <string>

// Decorador funcional para logging
auto logging_decorator = [](auto&& func) {
    return [func = std::forward<decltype(func)>(func)](auto&&... args) -> decltype(auto) {
        std::cout << "Llamando función con argumentos: ";
        ((std::cout << args << " "), ...);
        std::cout << std::endl;

        auto start = std::chrono::high_resolution_clock::now();
        decltype(auto) result = func(std::forward<decltype(args)>(args)...);
        auto end = std::chrono::high_resolution_clock::now();

        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        std::cout << "Función tomó " << duration.count() << " microsegundos" << std::endl;
        std::cout << "Resultado: " << result << std::endl;

        return std::forward<decltype(result)>(result);
    };
};

// Decorador funcional para caching
auto caching_decorator = [](auto&& func) {
    std::unordered_map<std::string, std::string> cache;

    return [func = std::forward<decltype(func)>(func), cache = std::move(cache)]
           (const std::string& key) mutable -> std::string {
        auto it = cache.find(key);
        if (it != cache.end()) {
            std::cout << "Usando resultado cacheado para: " << key << std::endl;
            return it->second;
        }

        std::cout << "Calculando resultado para: " << key << std::endl;
        std::string result = func(key);
        cache[key] = result;
        return result;
    };
};

// Función a decorar
std::string expensive_operation(const std::string& input) {
    std::cout << "Ejecutando operación costosa..." << std::endl;
    return "Resultado de: " + input;
}

// Aplicar decoradores
int main() {
    auto decorated_func = logging_decorator(caching_decorator(expensive_operation));

    // Primera llamada - calcula y cachea
    std::cout << "=== Primera llamada ===" << std::endl;
    std::string result1 = decorated_func("test1");

    // Segunda llamada - usa cache
    std::cout << "=== Segunda llamada ===" << std::endl;
    std::string result2 = decorated_func("test1");

    // Tercera llamada con diferente input
    std::cout << "=== Tercera llamada ===" << std::endl;
    std::string result3 = decorated_func("test2");

    return 0;
}

Ventajas del Decorator en C++

  • Herencia Múltiple: Soporte nativo sin necesidad de interfaces
  • Templates: Decorators type-safe y genéricos
  • Zero-Cost Abstractions: No hay overhead de runtime
  • RAII: Gestión automática de recursos
  • Move Semantics: Transferencia eficiente de ownership
  • Exception Safety: Control robusto de errores
  • Performance: Inlining de métodos decorados

Paso 4: Facade - Interfaz simplificada

El patrón Facade proporciona una interfaz simplificada para un subsistema complejo, haciendo que sea más fácil de usar y entender. En C++, este patrón aprovecha RAII para gestión automática de recursos y smart pointers para ownership claro.

Implementación básica con RAII

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

// Subsistema complejo
class DatabaseSubsystem {
public:
    DatabaseSubsystem() {
        std::cout << "Inicializando conexión a base de datos..." << std::endl;
    }

    ~DatabaseSubsystem() {
        std::cout << "Cerrando conexión a base de datos..." << std::endl;
    }

    std::string connect() {
        return "Conectado a base de datos";
    }

    std::string executeQuery(const std::string& query) {
        return "Ejecutando: " + query;
    }

    void disconnect() {
        std::cout << "Desconectado de base de datos" << std::endl;
    }
};

class CacheSubsystem {
public:
    CacheSubsystem() = default;

    std::string get(const std::string& key) {
        auto it = cache_.find(key);
        if (it != cache_.end()) {
            return "Obteniendo " + key + " del caché: " + it->second;
        }
        return "Clave " + key + " no encontrada en caché";
    }

    void put(const std::string& key, const std::string& value) {
        cache_[key] = value;
        std::cout << "Guardando " << key << ": " << value << " en caché" << std::endl;
    }

private:
    std::unordered_map<std::string, std::string> cache_;
};

class LoggerSubsystem {
public:
    LoggerSubsystem() = default;

    void log(const std::string& message) {
        std::cout << "LOG: " << message << std::endl;
    }
};

// Facade que simplifica el uso del subsistema con RAII
class DataServiceFacade {
public:
    DataServiceFacade()
        : db_(std::make_unique<DatabaseSubsystem>())
        , cache_(std::make_unique<CacheSubsystem>())
        , logger_(std::make_unique<LoggerSubsystem>()) {}

    // Interfaz simplificada que oculta la complejidad
    std::string getUserData(int user_id) {
        logger_->log("Obteniendo datos para usuario " + std::to_string(user_id));

        // Intentar obtener del caché primero
        std::string cache_key = "user_" + std::to_string(user_id);
        std::string cached_data = cache_->get(cache_key);

        if (cached_data.find("no encontrada") == std::string::npos) {
            return cached_data;
        }

        // Si no está en caché, obtener de BD
        db_->connect();
        std::string query = "SELECT * FROM usuarios WHERE id = " + std::to_string(user_id);
        std::string db_data = db_->executeQuery(query);
        db_->disconnect();

        // Guardar en caché para futuras consultas
        cache_->put(cache_key, db_data);

        return db_data;
    }

private:
    std::unique_ptr<DatabaseSubsystem> db_;
    std::unique_ptr<CacheSubsystem> cache_;
    std::unique_ptr<LoggerSubsystem> logger_;
};

// Uso del facade
int main() {
    {
        DataServiceFacade service;

        // Interfaz simple en lugar de manejar múltiples subsistemas
        std::string data = service.getUserData(123);
        std::cout << data << std::endl;
    } // RAII: Los subsistemas se destruyen automáticamente

    return 0;
}

Facade para compilador con gestión de recursos

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <sstream>

// Subsistemas del compilador
class LexicalAnalyzer {
public:
    LexicalAnalyzer() {
        std::cout << "Inicializando analizador léxico..." << std::endl;
    }

    ~LexicalAnalyzer() {
        std::cout << "Finalizando analizador léxico..." << std::endl;
    }

    std::vector<std::string> analyze(const std::string& source_code) {
        std::vector<std::string> tokens;
        std::istringstream iss(source_code);
        std::string token;

        while (iss >> token) {
            tokens.push_back(token);
        }

        std::cout << "Tokens extraídos: ";
        for (const auto& t : tokens) {
            std::cout << t << " ";
        }
        std::cout << std::endl;

        return tokens;
    }
};

class SyntaxAnalyzer {
public:
    SyntaxAnalyzer() {
        std::cout << "Inicializando analizador sintáctico..." << std::endl;
    }

    ~SyntaxAnalyzer() {
        std::cout << "Finalizando analizador sintáctico..." << std::endl;
    }

    std::string analyze(const std::vector<std::string>& tokens) {
        std::string tree = "Árbol sintáctico generado para: ";
        for (size_t i = 0; i < tokens.size(); ++i) {
            tree += tokens[i];
            if (i < tokens.size() - 1) tree += " ";
        }
        return tree;
    }
};

class CodeGenerator {
public:
    CodeGenerator() {
        std::cout << "Inicializando generador de código..." << std::endl;
    }

    ~CodeGenerator() {
        std::cout << "Finalizando generador de código..." << std::endl;
    }

    std::string generate(const std::string& syntax_tree) {
        return "Código objeto generado para: " + syntax_tree.substr(0, 50) + "...";
    }
};

class Optimizer {
public:
    Optimizer() {
        std::cout << "Inicializando optimizador..." << std::endl;
    }

    ~Optimizer() {
        std::cout << "Finalizando optimizador..." << std::endl;
    }

    std::string optimize(const std::string& code) {
        return "Código optimizado: " + code.substr(0, 30) + "... (optimizado)";
    }
};

// Facade para compilador con RAII completo
class CompilerFacade {
public:
    CompilerFacade()
        : lexer_(std::make_unique<LexicalAnalyzer>())
        , parser_(std::make_unique<SyntaxAnalyzer>())
        , generator_(std::make_unique<CodeGenerator>())
        , optimizer_(std::make_unique<Optimizer>()) {}

    std::string compile(const std::string& source_code, bool optimize = true) {
        std::cout << "=== Iniciando compilación ===" << std::endl;

        // Análisis léxico
        auto tokens = lexer_->analyze(source_code);
        std::cout << "✓ Análisis léxico completado" << std::endl;

        // Análisis sintáctico
        auto syntax_tree = parser_->analyze(tokens);
        std::cout << "✓ Análisis sintáctico completado" << std::endl;

        // Generación de código
        auto code = generator_->generate(syntax_tree);
        std::cout << "✓ Generación de código completado" << std::endl;

        // Optimización (opcional)
        if (optimize) {
            code = optimizer_->optimize(code);
            std::cout << "✓ Optimización completada" << std::endl;
        }

        std::cout << "=== Compilación finalizada ===" << std::endl;
        return code;
    }

private:
    std::unique_ptr<LexicalAnalyzer> lexer_;
    std::unique_ptr<SyntaxAnalyzer> parser_;
    std::unique_ptr<CodeGenerator> generator_;
    std::unique_ptr<Optimizer> optimizer_;
};

// Uso del facade
int main() {
    {
        CompilerFacade compiler;

        std::string source_code = "int suma(int a, int b) { return a + b; }";
        std::string result = compiler.compile(source_code, true);

        std::cout << "Resultado: " << result << std::endl;
    } // RAII: Todos los subsistemas se destruyen automáticamente

    return 0;
}

Facade para sistema de archivos con templates

#include <iostream>
#include <memory>
#include <string>
#include <fstream>
#include <vector>
#include <filesystem>

// Subsistema de archivos
class FileManager {
public:
    bool writeFile(const std::string& filename, const std::string& content) {
        std::ofstream file(filename);
        if (file.is_open()) {
            file << content;
            return true;
        }
        return false;
    }

    std::string readFile(const std::string& filename) {
        std::ifstream file(filename);
        if (file.is_open()) {
            std::string content((std::istreambuf_iterator<char>(file)),
                               std::istreambuf_iterator<char>());
            return content;
        }
        return "";
    }
};

// Subsistema de compresión
class CompressionManager {
public:
    std::string compress(const std::string& data) {
        return "COMPRESSED:" + data; // Simulación
    }

    std::string decompress(const std::string& data) {
        if (data.find("COMPRESSED:") == 0) {
            return data.substr(11);
        }
        return data;
    }
};

// Subsistema de encriptación
class EncryptionManager {
public:
    std::string encrypt(const std::string& data) {
        std::string encrypted = data;
        for (char& c : encrypted) {
            c += 1; // Simulación simple
        }
        return "ENCRYPTED:" + encrypted;
    }

    std::string decrypt(const std::string& data) {
        if (data.find("ENCRYPTED:") == 0) {
            std::string decrypted = data.substr(10);
            for (char& c : decrypted) {
                c -= 1;
            }
            return decrypted;
        }
        return data;
    }
};

// Facade genérico para sistema de archivos seguro
template<typename StoragePolicy>
class SecureFileFacade {
public:
    SecureFileFacade()
        : file_manager_(std::make_unique<FileManager>())
        , compression_(std::make_unique<CompressionManager>())
        , encryption_(std::make_unique<EncryptionManager>())
        , storage_policy_(std::make_unique<StoragePolicy>()) {}

    bool saveSecureFile(const std::string& filename, const std::string& content) {
        std::cout << "Guardando archivo seguro: " << filename << std::endl;

        // Aplicar compresión
        auto compressed = compression_->compress(content);

        // Aplicar encriptación
        auto encrypted = encryption_->encrypt(compressed);

        // Aplicar política de almacenamiento
        auto processed = storage_policy_->process(encrypted);

        // Guardar archivo
        return file_manager_->writeFile(filename, processed);
    }

    std::string loadSecureFile(const std::string& filename) {
        std::cout << "Cargando archivo seguro: " << filename << std::endl;

        // Leer archivo
        auto content = file_manager_->readFile(filename);

        // Aplicar política de almacenamiento
        auto processed = storage_policy_->process(content);

        // Desencriptar
        auto decrypted = encryption_->decrypt(processed);

        // Descomprimir
        auto decompressed = compression_->decompress(decrypted);

        return decompressed;
    }

private:
    std::unique_ptr<FileManager> file_manager_;
    std::unique_ptr<CompressionManager> compression_;
    std::unique_ptr<EncryptionManager> encryption_;
    std::unique_ptr<StoragePolicy> storage_policy_;
};

// Políticas de almacenamiento
class CloudStoragePolicy {
public:
    std::string process(const std::string& data) {
        return "CLOUD:" + data;
    }
};

class LocalStoragePolicy {
public:
    std::string process(const std::string& data) {
        return "LOCAL:" + data;
    }
};

// Uso del facade
int main() {
    // Facade para almacenamiento local
    SecureFileFacade<LocalStoragePolicy> local_storage;

    std::string content = "Datos sensibles que requieren protección";
    bool saved = local_storage.saveSecureFile("documento_seguro.txt", content);

    if (saved) {
        std::string loaded = local_storage.loadSecureFile("documento_seguro.txt");
        std::cout << "Contenido cargado: " << loaded << std::endl;
    }

    return 0;
}

Ventajas del Facade en C++

  • RAII: Gestión automática de recursos de subsistemas
  • Smart Pointers: Ownership claro de componentes
  • Templates: Facades genéricos y type-safe
  • Zero-Cost Abstractions: No hay overhead de runtime
  • Exception Safety: Control robusto de errores
  • Resource Management: Limpieza automática de recursos
  • Type Safety: Interfaces fuertemente tipadas

Paso 5: Proxy - Control de acceso

El patrón Proxy proporciona un sustituto o intermediario para controlar el acceso a un objeto, añadiendo lógica adicional como caching, logging, o control de acceso. En C++, este patrón es especialmente poderoso con smart pointers y RAII.

Proxy virtual (lazy loading) con smart pointers

#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <chrono>

// Interfaz base
class Image {
public:
    virtual ~Image() = default;
    virtual void display() = 0;
    virtual std::string getFilename() const = 0;
};

// Objeto real costoso de crear
class RealImage : public Image {
public:
    explicit RealImage(const std::string& filename)
        : filename_(filename) {
        loadFromDisk();
    }

    void display() override {
        std::cout << "Mostrando imagen: " << filename_ << std::endl;
    }

    std::string getFilename() const override {
        return filename_;
    }

private:
    void loadFromDisk() {
        std::cout << "Cargando imagen " << filename_ << " desde disco..." << std::endl;
        // Simular carga costosa
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Imagen " << filename_ << " cargada exitosamente" << std::endl;
    }

    std::string filename_;
};

// Proxy con lazy loading
class ImageProxy : public Image {
public:
    explicit ImageProxy(const std::string& filename)
        : filename_(filename)
        , real_image_(nullptr) {}

    ~ImageProxy() override {
        // RAII: El shared_ptr se encarga de la limpieza
    }

    void display() override {
        // Lazy loading: crear el objeto real solo cuando sea necesario
        if (!real_image_) {
            real_image_ = std::make_shared<RealImage>(filename_);
        }
        real_image_->display();
    }

    std::string getFilename() const override {
        return filename_;
    }

private:
    std::string filename_;
    std::shared_ptr<RealImage> real_image_;
};

// Uso del proxy
int main() {
    std::cout << "=== Creando proxies de imagen ===" << std::endl;
    auto image1 = std::make_unique<ImageProxy>("foto1.jpg");
    auto image2 = std::make_unique<ImageProxy>("foto2.jpg");

    std::cout << "Proxies creados (imágenes no cargadas aún)" << std::endl;
    std::cout << std::endl;

    std::cout << "=== Primera visualización ===" << std::endl;
    image1->display();  // Carga y muestra
    std::cout << std::endl;

    std::cout << "=== Segunda visualización (misma imagen) ===" << std::endl;
    image1->display();  // Solo muestra (ya cargada)
    std::cout << std::endl;

    std::cout << "=== Visualización de segunda imagen ===" << std::endl;
    image2->display();  // Carga y muestra

    return 0;
}

Proxy de protección con control de acceso

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

// Servicio real
class BankService {
public:
    BankService(double initial_balance = 1000.0)
        : balance_(initial_balance) {}

    std::string withdraw(double amount, const std::string& user) {
        if (amount <= balance_) {
            balance_ -= amount;
            return "Retirados $" + std::to_string(amount) +
                   ". Saldo restante: $" + std::to_string(balance_);
        }
        return "Saldo insuficiente";
    }

    std::string checkBalance(const std::string& user) {
        return "Saldo actual: $" + std::to_string(balance_);
    }

private:
    double balance_;
};

// Proxy de protección
class BankProxy {
public:
    BankProxy(std::unique_ptr<BankService> real_service, const std::string& authorized_user)
        : real_service_(std::move(real_service))
        , authorized_user_(authorized_user) {}

    std::string withdraw(double amount, const std::string& user) {
        if (verifyAccess(user)) {
            return real_service_->withdraw(amount, user);
        }
        return "Acceso denegado: Usuario no autorizado";
    }

    std::string checkBalance(const std::string& user) {
        if (verifyAccess(user)) {
            return real_service_->checkBalance(user);
        }
        return "Acceso denegado: Usuario no autorizado";
    }

private:
    bool verifyAccess(const std::string& user) {
        return user == authorized_user_;
    }

    std::unique_ptr<BankService> real_service_;
    std::string authorized_user_;
};

// Uso del proxy de protección
int main() {
    auto real_bank = std::make_unique<BankService>();
    auto bank_proxy = std::make_unique<BankProxy>(std::move(real_bank), "usuario123");

    std::cout << "=== Acceso autorizado ===" << std::endl;
    std::cout << bank_proxy->checkBalance("usuario123") << std::endl;
    std::cout << bank_proxy->withdraw(200.0, "usuario123") << std::endl;

    std::cout << std::endl << "=== Acceso no autorizado ===" << std::endl;
    std::cout << bank_proxy->checkBalance("usuario456") << std::endl;
    std::cout << bank_proxy->withdraw(100.0, "usuario456") << std::endl;

    return 0;
}

Proxy de caching con thread safety

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <thread>
#include <chrono>

// Servicio web real
class WebService {
public:
    std::string fetchData(const std::string& url) {
        std::cout << "Haciendo petición HTTP a " << url << std::endl;
        // Simular petición costosa
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        return "Datos de " + url;
    }
};

// Proxy de caching thread-safe
class CachedWebServiceProxy {
public:
    explicit CachedWebServiceProxy(std::unique_ptr<WebService> real_service)
        : real_service_(std::move(real_service)) {}

    std::string fetchData(const std::string& url) {
        // Búsqueda en caché (lectura thread-safe)
        {
            std::shared_lock<std::shared_mutex> lock(cache_mutex_);
            auto it = cache_.find(url);
            if (it != cache_.end()) {
                std::cout << "Obteniendo " << url << " del caché" << std::endl;
                return it->second;
            }
        }

        // Si no está en caché, obtener del servicio real
        auto data = real_service_->fetchData(url);

        // Guardar en caché (escritura thread-safe)
        {
            std::unique_lock<std::shared_mutex> lock(cache_mutex_);
            cache_[url] = data;
        }

        return data;
    }

private:
    std::unique_ptr<WebService> real_service_;
    std::unordered_map<std::string, std::string> cache_;
    std::shared_mutex cache_mutex_;  // Para acceso concurrente
};

// Función para simular múltiples hilos
void clientRequest(CachedWebServiceProxy& proxy, const std::string& url, int client_id) {
    std::cout << "Cliente " << client_id << " solicitando: " << url << std::endl;
    auto data = proxy.fetchData(url);
    std::cout << "Cliente " << client_id << " recibió: " << data << std::endl;
}

// Uso del proxy de caching thread-safe
int main() {
    auto real_service = std::make_unique<WebService>();
    CachedWebServiceProxy proxy(std::move(real_service));

    std::cout << "=== Primera petición (va al servicio real) ===" << std::endl;
    auto data1 = proxy.fetchData("https://api.example.com/users");

    std::cout << std::endl << "=== Segunda petición (usa caché) ===" << std::endl;
    auto data2 = proxy.fetchData("https://api.example.com/users");

    std::cout << std::endl << "=== Pruebas de concurrencia ===" << std::endl;
    std::vector<std::thread> threads;

    // Múltiples hilos solicitando la misma URL
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(clientRequest, std::ref(proxy),
                           "https://api.example.com/products", i + 1);
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

Proxy inteligente con logging y timing

#include <iostream>
#include <memory>
#include <string>
#include <chrono>
#include <functional>

// Interfaz base
class Database {
public:
    virtual ~Database() = default;
    virtual std::string query(const std::string& sql) = 0;
};

// Base de datos real
class RealDatabase : public Database {
public:
    std::string query(const std::string& sql) override {
        // Simular trabajo de BD
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        return "Resultado de: " + sql;
    }
};

// Proxy inteligente con logging y timing
class SmartDatabaseProxy : public Database {
public:
    explicit SmartDatabaseProxy(std::unique_ptr<Database> real_db)
        : real_db_(std::move(real_db))
        , query_count_(0) {}

    std::string query(const std::string& sql) override {
        auto start_time = std::chrono::high_resolution_clock::now();

        std::cout << "[LOG] Ejecutando query: " << sql << std::endl;
        auto result = real_db_->query(sql);

        auto end_time = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);

        query_count_++;
        std::cout << "[LOG] Query completada en " << duration.count() << " microsegundos" << std::endl;
        std::cout << "[LOG] Total de queries ejecutadas: " << query_count_ << std::endl;

        return result;
    }

private:
    std::unique_ptr<Database> real_db_;
    int query_count_;
};

// Uso del proxy inteligente
int main() {
    auto real_db = std::make_unique<RealDatabase>();
    SmartDatabaseProxy smart_proxy(std::move(real_db));

    std::vector<std::string> queries = {
        "SELECT * FROM users",
        "SELECT * FROM products",
        "INSERT INTO orders VALUES (1, 'order1')",
        "SELECT * FROM users"  // Query repetida para mostrar logging
    };

    for (const auto& query : queries) {
        std::cout << std::endl;
        auto result = smart_proxy.query(query);
        std::cout << "Resultado: " << result << std::endl;
    }

    return 0;
}

Ventajas del Proxy en C++

  • Smart Pointers: Gestión automática de memoria y ownership
  • RAII: Limpieza automática de recursos
  • Thread Safety: Soporte nativo para concurrencia
  • Zero-Cost Abstractions: No hay overhead cuando no se usa
  • Templates: Proxies genéricos y type-safe
  • Exception Safety: Control robusto de errores
  • Performance: Inlining de métodos proxy
  • Memory Management: Prevención de leaks con smart pointers

Paso 6: Bridge - Separar abstracción e implementación

El patrón Bridge separa una abstracción de su implementación, permitiendo que ambas varíen independientemente. En C++, este patrón es especialmente poderoso con templates y herencia múltiple.

Implementación básica con templates

#include <iostream>
#include <memory>
#include <string>

// Implementación base genérica
template<typename T>
class DrawingImplementation {
public:
    virtual ~DrawingImplementation() = default;
    virtual std::string drawLine(T x1, T y1, T x2, T y2) = 0;
    virtual std::string drawCircle(T x, T y, T radius) = 0;
};

// Implementaciones concretas
class VectorDrawing : public DrawingImplementation<double> {
public:
    std::string drawLine(double x1, double y1, double x2, double y2) override {
        return "Dibujando línea vectorial: (" + std::to_string(x1) + "," +
               std::to_string(y1) + ") -> (" + std::to_string(x2) + "," +
               std::to_string(y2) + ")";
    }

    std::string drawCircle(double x, double y, double radius) override {
        return "Dibujando círculo vectorial: centro=(" + std::to_string(x) + "," +
               std::to_string(y) + "), radio=" + std::to_string(radius);
    }
};

class RasterDrawing : public DrawingImplementation<int> {
public:
    std::string drawLine(int x1, int y1, int x2, int y2) override {
        return "Dibujando línea raster: (" + std::to_string(x1) + "," +
               std::to_string(y1) + ") -> (" + std::to_string(x2) + "," +
               std::to_string(y2) + ")";
    }

    std::string drawCircle(int x, int y, int radius) override {
        return "Dibujando círculo raster: centro=(" + std::to_string(x) + "," +
               std::to_string(y) + "), radio=" + std::to_string(radius);
    }
};

// Abstracción base
template<typename T>
class Shape {
public:
    explicit Shape(std::unique_ptr<DrawingImplementation<T>> implementation)
        : implementation_(std::move(implementation)) {}

    virtual ~Shape() = default;

    virtual std::string draw() = 0;

protected:
    std::unique_ptr<DrawingImplementation<T>> implementation_;
};

// Abstracciones refinadas
class Circle : public Shape<double> {
public:
    Circle(std::unique_ptr<DrawingImplementation<double>> implementation,
           double x, double y, double radius)
        : Shape<double>(std::move(implementation))
        , x_(x), y_(y), radius_(radius) {}

    std::string draw() override {
        return implementation_->drawCircle(x_, y_, radius_);
    }

private:
    double x_, y_, radius_;
};

class Rectangle : public Shape<int> {
public:
    Rectangle(std::unique_ptr<DrawingImplementation<int>> implementation,
              int x1, int y1, int x2, int y2)
        : Shape<int>(std::move(implementation))
        , x1_(x1), y1_(y1), x2_(x2), y2_(y2) {}

    std::string draw() override {
        std::string result = "Rectángulo:\n";
        result += implementation_->drawLine(x1_, y1_, x2_, y1_) + "\n";
        result += implementation_->drawLine(x2_, y1_, x2_, y2_) + "\n";
        result += implementation_->drawLine(x2_, y2_, x1_, y2_) + "\n";
        result += implementation_->drawLine(x1_, y2_, x1_, y1_);
        return result;
    }

private:
    int x1_, y1_, x2_, y2_;
};

// Uso del Bridge
int main() {
    auto vector_drawing = std::make_unique<VectorDrawing>();
    auto raster_drawing = std::make_unique<RasterDrawing>();

    Circle circle(std::move(vector_drawing), 10.5, 10.5, 5.0);
    Rectangle rectangle(std::move(raster_drawing), 0, 0, 20, 10);

    std::cout << circle.draw() << std::endl;
    std::cout << std::endl;
    std::cout << rectangle.draw() << std::endl;

    return 0;
}

Bridge para dispositivos y sistemas operativos

#include <iostream>
#include <memory>
#include <string>
#include <vector>

// Implementación: Sistemas Operativos
class OperatingSystem {
public:
    virtual ~OperatingSystem() = default;
    virtual std::string loadDriver(const std::string& driver_name) = 0;
    virtual std::string executeCommand(const std::string& command) = 0;
    virtual std::string getSystemInfo() = 0;
};

class WindowsOS : public OperatingSystem {
public:
    std::string loadDriver(const std::string& driver_name) override {
        return "Cargando driver " + driver_name + " en Windows";
    }

    std::string executeCommand(const std::string& command) override {
        return "Ejecutando en Windows: " + command;
    }

    std::string getSystemInfo() override {
        return "Windows 11 Pro - Sistema operativo de escritorio";
    }
};

class LinuxOS : public OperatingSystem {
public:
    std::string loadDriver(const std::string& driver_name) override {
        return "Cargando driver " + driver_name + " en Linux";
    }

    std::string executeCommand(const std::string& command) override {
        return "Ejecutando en Linux: " + command;
    }

    std::string getSystemInfo() override {
        return "Ubuntu 22.04 LTS - Sistema operativo de servidor";
    }
};

class MacOS : public OperatingSystem {
public:
    std::string loadDriver(const std::string& driver_name) override {
        return "Cargando driver " + driver_name + " en macOS";
    }

    std::string executeCommand(const std::string& command) override {
        return "Ejecutando en macOS: " + command;
    }

    std::string getSystemInfo() override {
        return "macOS Monterey - Sistema operativo de desarrollo";
    }
};

// Abstracción: Dispositivos
class Device {
public:
    explicit Device(std::unique_ptr<OperatingSystem> os)
        : os_(std::move(os)) {}

    virtual ~Device() = default;

    virtual std::string boot() = 0;
    virtual std::string runDiagnostics() = 0;

protected:
    std::unique_ptr<OperatingSystem> os_;
};

// Dispositivos concretos
class DesktopComputer : public Device {
public:
    explicit DesktopComputer(std::unique_ptr<OperatingSystem> os)
        : Device(std::move(os)) {}

    std::string boot() override {
        return "Iniciando computadora de escritorio...\n" +
               os_->getSystemInfo() + "\n" +
               os_->loadDriver("graphics_driver") + "\n" +
               os_->executeCommand("start_gui");
    }

    std::string runDiagnostics() override {
        return "Ejecutando diagnóstico de desktop...\n" +
               os_->executeCommand("check_hardware") + "\n" +
               os_->executeCommand("memory_test");
    }
};

class MobilePhone : public Device {
public:
    explicit MobilePhone(std::unique_ptr<OperatingSystem> os)
        : Device(std::move(os)) {}

    std::string boot() override {
        return "Iniciando teléfono móvil...\n" +
               os_->getSystemInfo() + "\n" +
               os_->loadDriver("touch_driver") + "\n" +
               os_->executeCommand("start_mobile_ui");
    }

    std::string runDiagnostics() override {
        return "Ejecutando diagnóstico de móvil...\n" +
               os_->executeCommand("battery_check") + "\n" +
               os_->executeCommand("network_test");
    }
};

class EmbeddedSystem : public Device {
public:
    explicit EmbeddedSystem(std::unique_ptr<OperatingSystem> os)
        : Device(std::move(os)) {}

    std::string boot() override {
        return "Iniciando sistema embebido...\n" +
               os_->getSystemInfo() + "\n" +
               os_->loadDriver("embedded_driver") + "\n" +
               os_->executeCommand("start_embedded_app");
    }

    std::string runDiagnostics() override {
        return "Ejecutando diagnóstico embebido...\n" +
               os_->executeCommand("sensor_check") + "\n" +
               os_->executeCommand("io_test");
    }
};

// Uso del Bridge
int main() {
    std::vector<std::unique_ptr<Device>> devices;

    // Crear dispositivos con diferentes sistemas operativos
    devices.push_back(std::make_unique<DesktopComputer>(std::make_unique<WindowsOS>()));
    devices.push_back(std::make_unique<DesktopComputer>(std::make_unique<LinuxOS>()));
    devices.push_back(std::make_unique<MobilePhone>(std::make_unique<MacOS>()));
    devices.push_back(std::make_unique<EmbeddedSystem>(std::make_unique<LinuxOS>()));

    std::cout << "=== Probando diferentes combinaciones de dispositivos y OS ===" << std::endl;

    for (size_t i = 0; i < devices.size(); ++i) {
        std::cout << "\n--- Dispositivo " << (i + 1) << " ---" << std::endl;
        std::cout << devices[i]->boot() << std::endl;
        std::cout << devices[i]->runDiagnostics() << std::endl;
    }

    return 0;
}

Ventajas del Bridge en C++

  • Templates: Implementaciones genéricas y type-safe
  • Zero-Cost Abstractions: No hay overhead de runtime
  • RAII: Gestión automática de recursos
  • Smart Pointers: Ownership claro de implementaciones
  • Multiple Inheritance: Soporte nativo para abstracciones complejas
  • Exception Safety: Control robusto de errores
  • Performance: Inlining de métodos bridge
  • Flexibilidad: Fácil extensión de abstracciones e implementaciones

Composite para sistema de archivos

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>
#include <chrono>

// Interfaz base para elementos del sistema de archivos
class FileSystemElement {
public:
    explicit FileSystemElement(const std::string& name) : name_(name) {}
    virtual ~FileSystemElement() = default;

    // Operaciones comunes
    virtual size_t getSize() const = 0;
    virtual void display(int indent = 0) const = 0;
    virtual std::string getName() const { return name_; }

    // Método para verificar si es un directorio (útil para sorting)
    virtual bool isDirectory() const = 0;

protected:
    std::string name_;
};

// Hoja: Archivo
class File : public FileSystemElement {
public:
    File(const std::string& name, size_t size_in_bytes)
        : FileSystemElement(name), size_in_bytes_(size_in_bytes) {}

    size_t getSize() const override {
        return size_in_bytes_;
    }

    void display(int indent) const override {
        std::string indentation(indent * 2, ' ');
        std::cout << indentation << "📄 " << getName()
                  << " (" << (size_in_bytes_ / 1024) << " KB)" << std::endl;
    }

    bool isDirectory() const override {
        return false;
    }

private:
    size_t size_in_bytes_;
};

// Composite: Directorio
class Directory : public FileSystemElement {
public:
    explicit Directory(const std::string& name) : FileSystemElement(name) {}

    // Gestión de elementos hijos
    void addElement(std::unique_ptr<FileSystemElement> element) {
        elements_.push_back(std::move(element));
        updateModificationTime();
    }

    bool removeElement(const std::string& name) {
        auto it = std::find_if(elements_.begin(), elements_.end(),
            [&name](const std::unique_ptr<FileSystemElement>& elem) {
                return elem->getName() == name;
            });

        if (it != elements_.end()) {
            elements_.erase(it);
            updateModificationTime();
            return true;
        }
        return false;
    }

    FileSystemElement* getElement(const std::string& name) {
        auto it = std::find_if(elements_.begin(), elements_.end(),
            [&name](const std::unique_ptr<FileSystemElement>& elem) {
                return elem->getName() == name;
            });

        return (it != elements_.end()) ? it->get() : nullptr;
    }

    size_t getSize() const override {
        size_t total_size = 0;
        for (const auto& element : elements_) {
            total_size += element->getSize();
        }
        return total_size;
    }

    void display(int indent = 0) const override {
        std::string indentation(indent * 2, ' ');
        std::cout << indentation << "📁 " << getName() << "/" << std::endl;

        // Ordenar elementos: directorios primero, luego archivos
        std::vector<std::pair<bool, const FileSystemElement*>> sorted_elements;
        for (const auto& element : elements_) {
            sorted_elements.emplace_back(element->isDirectory(), element.get());
        }

        std::sort(sorted_elements.begin(), sorted_elements.end(),
                  std::greater<std::pair<bool, const FileSystemElement*>>());

        for (const auto& [is_dir, element] : sorted_elements) {
            element->display(indent + 1);
        }
    }

    const std::vector<std::unique_ptr<FileSystemElement>>& getElements() const {
        return elements_;
    }

    bool isDirectory() const override {
        return true;
    }

private:
    void updateModificationTime() {
        // En una implementación real, actualizaría timestamp
        last_modified_ = std::chrono::system_clock::now();
    }

    std::vector<std::unique_ptr<FileSystemElement>> elements_;
    std::chrono::system_clock::time_point last_modified_;
};

// Uso del patrón Composite
int main() {
    // Crear estructura de archivos
    auto root = std::make_unique<Directory>("MiProyecto");

    // Crear subdirectorios
    auto src = std::make_unique<Directory>("src");
    auto docs = std::make_unique<Directory>("docs");
    auto assets = std::make_unique<Directory>("assets");

    // Crear archivos
    auto main_py = std::make_unique<File>("main.py", 15 * 1024);      // 15 KB
    auto utils_py = std::make_unique<File>("utils.py", 8 * 1024);     // 8 KB
    auto config_py = std::make_unique<File>("config.py", 5 * 1024);    // 5 KB
    auto readme = std::make_unique<File>("README.md", 3 * 1024);       // 3 KB
    auto manual = std::make_unique<File>("manual.pdf", 250 * 1024);    // 250 KB
    auto logo = std::make_unique<File>("logo.jpg", 1200 * 1024);      // 1200 KB
    auto icon = std::make_unique<File>("icon.png", 45 * 1024);        // 45 KB
    auto audio = std::make_unique<File>("background.mp3", 3500 * 1024); // 3500 KB

    // Construir estructura jerárquica
    src->addElement(std::move(main_py));
    src->addElement(std::move(utils_py));
    src->addElement(std::move(config_py));

    docs->addElement(std::move(readme));
    docs->addElement(std::move(manual));

    assets->addElement(std::move(logo));
    assets->addElement(std::move(icon));
    assets->addElement(std::move(audio));

    root->addElement(std::move(src));
    root->addElement(std::move(docs));
    root->addElement(std::move(assets));

    // Mostrar estructura completa
    std::cout << "=== Estructura del Sistema de Archivos ===" << std::endl;
    root->display();

    // Calcular estadísticas
    std::cout << std::endl << "=== Estadísticas ===" << std::endl;
    std::cout << "Tamaño total del proyecto: " << (root->getSize() / 1024) << " KB" << std::endl;

    // Contar archivos y directorios
    size_t file_count = 0;
    size_t dir_count = 0;
    std::unordered_map<std::string, int> file_types;

    std::function<void(const FileSystemElement*)> countElements =
        [&](const FileSystemElement* element) {
            if (element->isDirectory()) {
                dir_count++;
                const Directory* dir = static_cast<const Directory*>(element);
                for (const auto& child : dir->getElements()) {
                    countElements(child.get());
                }
            } else {
                file_count++;
                const File* file = static_cast<const File*>(element);
                std::string ext = file->getName();
                size_t dot_pos = ext.find_last_of('.');
                if (dot_pos != std::string::npos) {
                    std::string extension = ext.substr(dot_pos + 1);
                    file_types[extension]++;
                }
            }
        };

    countElements(root.get());

    std::cout << "Archivos: " << file_count << std::endl;
    std::cout << "Directorios: " << dir_count << std::endl;
    std::cout << "Tipos de archivo:" << std::endl;
    for (const auto& [ext, count] : file_types) {
        std::cout << "  " << ext << ": " << count << std::endl;
    }

    // Demostrar operaciones del Composite
    std::cout << std::endl << "=== Operaciones del Composite ===" << std::endl;

    // Buscar archivos Python
    std::cout << "Archivos Python:" << std::endl;
    std::function<void(const FileSystemElement*, int)> findPythonFiles =
        [&](const FileSystemElement* element, int indent) {
            std::string indentation(indent * 2, ' ');
            if (!element->isDirectory()) {
                const File* file = static_cast<const File*>(element);
                if (file->getName().find(".py") != std::string::npos) {
                    std::cout << indentation << "🐍 " << file->getName() << std::endl;
                }
            } else {
                const Directory* dir = static_cast<const Directory*>(element);
                for (const auto& child : dir->getElements()) {
                    findPythonFiles(child.get(), indent + 1);
                }
            }
        };

    findPythonFiles(root.get(), 0);

    return 0;
}

Paso 8: Flyweight - Compartir objetos eficientemente

El patrón Flyweight permite compartir objetos eficientemente, reduciendo el uso de memoria cuando tienes muchos objetos similares. En C++, este patrón es especialmente poderoso con std::shared_ptr para compartir objetos y templates para implementaciones genéricas.

Implementación básica con smart pointers

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>

// Estado intrínseco (compartido) - Flyweight base
class CharacterType {
public:
    CharacterType(const std::string& font, int size, const std::string& color)
        : font_(font), size_(size), color_(color) {}

    void display(int position) const {
        std::cout << "Caracter en fuente " << font_ << ", tamaño " << size_
                  << ", color " << color_ << " en posición " << position << std::endl;
    }

    // Operador de igualdad para usar como clave en unordered_map
    bool operator==(const CharacterType& other) const {
        return font_ == other.font_ && size_ == other.size_ && color_ == other.color_;
    }

private:
    std::string font_;
    int size_;
    std::string color_;
};

// Hash para usar CharacterType como clave
struct CharacterTypeHash {
    std::size_t operator()(const CharacterType& type) const {
        return std::hash<std::string>()(type.font_) ^
               std::hash<int>()(type.size_) ^
               std::hash<std::string>()(type.color_);
    }
};

// Fábrica de flyweights con thread safety
class CharacterFactory {
public:
    std::shared_ptr<CharacterType> getCharacterType(const std::string& font, int size, const std::string& color) {
        CharacterType key(font, size, color);

        std::lock_guard<std::mutex> lock(mutex_);

        auto it = flyweights_.find(key);
        if (it == flyweights_.end()) {
            auto flyweight = std::make_shared<CharacterType>(font, size, color);
            flyweights_[key] = flyweight;
            std::cout << "Creando nuevo tipo de caracter: " << font << ", " << size << ", " << color << std::endl;
            return flyweight;
        } else {
            std::cout << "Reutilizando tipo de caracter: " << font << ", " << size << ", " << color << std::endl;
            return it->second;
        }
    }

    size_t getFlyweightCount() const {
        return flyweights_.size();
    }

private:
    std::unordered_map<CharacterType, std::shared_ptr<CharacterType>, CharacterTypeHash> flyweights_;
    std::mutex mutex_;  // Para thread safety
};

// Estado extrínseco (no compartido) - Contexto
class Character {
public:
    Character(std::shared_ptr<CharacterType> type, int position)
        : type_(type), position_(position) {}

    void display() {
        type_->display(position_);
    }

private:
    std::shared_ptr<CharacterType> type_;
    int position_;  // Estado extrínseco
};

// Uso del patrón Flyweight
int main() {
    CharacterFactory factory;

    std::vector<Character> characters;

    // Crear muchos caracteres con pocos tipos compartidos
    for (int i = 0; i < 100; ++i) {
        std::shared_ptr<CharacterType> type;

        // Solo 3 tipos diferentes de caracteres
        if (i % 3 == 0) {
            type = factory.getCharacterType("Arial", 12, "Black");
        } else if (i % 3 == 1) {
            type = factory.getCharacterType("Times", 14, "Blue");
        } else {
            type = factory.getCharacterType("Courier", 10, "Red");
        }

        characters.emplace_back(type, i);
    }

    std::cout << std::endl;
    std::cout << "Total de caracteres: " << characters.size() << std::endl;
    std::cout << "Total de tipos de caracter: " << factory.getFlyweightCount() << std::endl;
    std::cout << "Memoria ahorrada: " << (characters.size() - factory.getFlyweightCount()) << " objetos" << std::endl;

    // Mostrar algunos caracteres
    std::cout << std::endl << "Mostrando algunos caracteres:" << std::endl;
    for (int i = 0; i < 5; ++i) {
        characters[i].display();
    }

    return 0;
}

Flyweight con templates para mayor flexibilidad

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <functional>

// Flyweight genérico con template
template<typename Key, typename... Args>
class Flyweight {
public:
    virtual ~Flyweight() = default;
    virtual void operation(const Args&... args) = 0;
};

// Implementación concreta del flyweight
class TextStyle : public Flyweight<std::tuple<std::string, int, std::string>, int> {
public:
    TextStyle(const std::string& font, int size, const std::string& color)
        : font_(font), size_(size), color_(color) {}

    void operation(int position) override {
        std::cout << "Texto en fuente " << font_ << ", tamaño " << size_
                  << ", color " << color_ << " en posición " << position << std::endl;
    }

private:
    std::string font_;
    int size_;
    std::string color_;
};

// Fábrica de flyweights genérica
template<typename Key, typename FlyweightType, typename... Args>
class FlyweightFactory {
public:
    std::shared_ptr<FlyweightType> getFlyweight(const Key& key) {
        std::lock_guard<std::mutex> lock(mutex_);

        auto it = flyweights_.find(key);
        if (it == flyweights_.end()) {
            auto flyweight = std::make_shared<FlyweightType>(key);
            flyweights_[key] = flyweight;
            std::cout << "Creando nuevo flyweight" << std::endl;
            return flyweight;
        } else {
            std::cout << "Reutilizando flyweight existente" << std::endl;
            return it->second;
        }
    }

    size_t getFlyweightCount() const {
        return flyweights_.size();
    }

private:
    std::unordered_map<Key, std::shared_ptr<FlyweightType>> flyweights_;
    std::mutex mutex_;
};

// Contexto que usa flyweights
class FormattedText {
public:
    FormattedText(FlyweightFactory<std::tuple<std::string, int, std::string>, TextStyle, int>& factory)
        : factory_(factory) {}

    void addCharacter(char character, const std::string& font, int size, const std::string& color, int position) {
        auto key = std::make_tuple(font, size, color);
        auto style = factory_.getFlyweight(key);
        characters_.push_back({character, style, position});
    }

    void display() {
        for (const auto& [character, style, position] : characters_) {
            std::cout << character << " ";
            style->operation(position);
        }
    }

private:
    FlyweightFactory<std::tuple<std::string, int, std::string>, TextStyle, int>& factory_;
    std::vector<std::tuple<char, std::shared_ptr<TextStyle>, int>> characters_;
};

// Uso del flyweight con templates
int main() {
    FlyweightFactory<std::tuple<std::string, int, std::string>, TextStyle, int> factory;

    FormattedText text(factory);

    // Agregar texto con diferentes estilos
    std::string sample_text = "Hola Mundo";
    int position = 0;

    for (char c : sample_text) {
        if (c == 'H' || c == 'M') {
            text.addCharacter(c, "Arial", 14, "Bold", position++);
        } else if (c == 'o' || c == 'u') {
            text.addCharacter(c, "Times", 12, "Italic", position++);
        } else {
            text.addCharacter(c, "Courier", 10, "Regular", position++);
        }
    }

    std::cout << "Texto formateado:" << std::endl;
    text.display();

    std::cout << std::endl;
    std::cout << "Total de estilos únicos: " << factory.getFlyweightCount() << std::endl;

    return 0;
}

Flyweight para tipos de archivo (caso real)

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <vector>
#include <filesystem>

// Flyweight para tipos de archivo
class FileType {
public:
    FileType(const std::string& extension, const std::string& description, const std::string& icon)
        : extension_(extension), description_(description), icon_(icon) {}

    std::string getInfo() const {
        return icon_ + " " + description_ + " (." + extension_ + ")";
    }

    std::string getExtension() const { return extension_; }
    std::string getDescription() const { return description_; }
    std::string getIcon() const { return icon_; }

private:
    std::string extension_;
    std::string description_;
    std::string icon_;
};

// Fábrica singleton de tipos de archivo
class FileTypeFactory {
public:
    static FileTypeFactory& getInstance() {
        static FileTypeFactory instance;
        return instance;
    }

    std::shared_ptr<FileType> getFileType(const std::string& extension) {
        std::string ext_lower = extension;
        std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);

        std::lock_guard<std::mutex> lock(mutex_);

        auto it = file_types_.find(ext_lower);
        if (it == file_types_.end()) {
            // Crear nuevo tipo de archivo
            std::string description = "Archivo " + extension;
            std::string icon = "📄";

            // Asignar iconos según extensión
            if (ext_lower == "txt") {
                description = "Archivo de texto";
                icon = "📄";
            } else if (ext_lower == "py" || ext_lower == "cpp" || ext_lower == "java") {
                description = "Archivo de código fuente";
                icon = "💻";
            } else if (ext_lower == "jpg" || ext_lower == "png" || ext_lower == "gif") {
                description = "Archivo de imagen";
                icon = "🖼️";
            } else if (ext_lower == "pdf") {
                description = "Documento PDF";
                icon = "📕";
            } else if (ext_lower == "mp3" || ext_lower == "wav") {
                description = "Archivo de audio";
                icon = "🎵";
            } else if (ext_lower == "mp4" || ext_lower == "avi") {
                description = "Archivo de video";
                icon = "🎬";
            }

            auto file_type = std::make_shared<FileType>(ext_lower, description, icon);
            file_types_[ext_lower] = file_type;
            std::cout << "Creando nuevo tipo de archivo: " << ext_lower << std::endl;
            return file_type;
        } else {
            std::cout << "Reutilizando tipo de archivo: " << ext_lower << std::endl;
            return it->second;
        }
    }

    size_t getFileTypeCount() const {
        return file_types_.size();
    }

private:
    FileTypeFactory() = default;
    std::unordered_map<std::string, std::shared_ptr<FileType>> file_types_;
    std::mutex mutex_;
};

// Archivo que usa flyweight para su tipo
class File {
public:
    File(const std::string& name, size_t size, const std::string& path = "")
        : name_(name), size_(size), path_(path) {

        size_t dot_pos = name_.find_last_of('.');
        if (dot_pos != std::string::npos) {
            std::string extension = name_.substr(dot_pos + 1);
            file_type_ = FileTypeFactory::getInstance().getFileType(extension);
        } else {
            file_type_ = FileTypeFactory::getInstance().getFileType("");
        }
    }

    void display() const {
        std::cout << file_type_->getInfo() << " - " << name_
                  << " (" << (size_ / 1024) << " KB)" << std::endl;
    }

    std::string getName() const { return name_; }
    size_t getSize() const { return size_; }
    std::string getPath() const { return path_; }
    std::shared_ptr<FileType> getFileType() const { return file_type_; }

private:
    std::string name_;
    size_t size_;
    std::string path_;
    std::shared_ptr<FileType> file_type_;  // Flyweight compartido
};

// Uso del flyweight para tipos de archivo
int main() {
    std::vector<File> files = {
        File("documento.txt", 5 * 1024),
        File("script.py", 15 * 1024),
        File("imagen.jpg", 1200 * 1024),
        File("manual.pdf", 250 * 1024),
        File("cancion.mp3", 3500 * 1024),
        File("video.mp4", 50000 * 1024),
        File("config.txt", 2 * 1024),      // Reutilizará el tipo .txt
        File("utilidades.py", 8 * 1024),   // Reutilizará el tipo .py
        File("foto.png", 800 * 1024),      // Reutilizará el tipo de imagen
    };

    std::cout << "=== Lista de archivos ===" << std::endl;
    for (const auto& file : files) {
        file.display();
    }

    std::cout << std::endl;
    std::cout << "Total de archivos: " << files.size() << std::endl;
    std::cout << "Total de tipos de archivo únicos: " << FileTypeFactory::getInstance().getFileTypeCount() << std::endl;
    std::cout << "Memoria ahorrada: " << (files.size() - FileTypeFactory::getInstance().getFileTypeCount()) << " objetos" << std::endl;

    // Mostrar estadísticas por tipo
    std::unordered_map<std::string, int> type_count;
    for (const auto& file : files) {
        type_count[file.getFileType()->getExtension()]++;
    }

    std::cout << std::endl << "Estadísticas por tipo:" << std::endl;
    for (const auto& [ext, count] : type_count) {
        std::cout << "  " << ext << ": " << count << " archivos" << std::endl;
    }

    return 0;
}

Ventajas del Flyweight en C++

  • Smart Pointers: std::shared_ptr para compartir objetos de manera segura
  • Thread Safety: Soporte nativo para concurrencia con mutex
  • Zero-Cost Abstractions: No hay overhead cuando se implementa correctamente
  • Templates: Implementaciones genéricas y type-safe
  • RAII: Gestión automática de memoria
  • Performance: Inlining y optimizaciones del compilador
  • Memory Efficiency: Reducción significativa del uso de memoria
  • Type Safety: Verificación en tiempo de compilación

Paso 9: Comparación y selección de patrones

Cuándo usar cada patrón estructural en C++

Patrón Cuándo usarlo Ventajas en C++ Desventajas en C++ Ejemplo de uso
Adapter Interfaces incompatibles, legacy code, APIs externas Templates para type-safe adapters, herencia múltiple nativa, zero-cost abstractions Añade indirección, puede complicar debugging Integración con APIs de C, legacy code, diferentes bibliotecas
Decorator Funcionalidad dinámica, logging, caching, validación Herencia múltiple nativa, templates genéricos, RAII automático Puede crear muchas clases pequeñas, stack overflow con demasiados decoradores Middleware, logging, caching, validación de datos
Facade Subsistemas complejos, APIs complicadas, inicialización RAII para gestión de recursos, smart pointers para ownership, templates genéricos Puede ocultar funcionalidad necesaria, testing más complejo Compiladores, sistemas de archivos, redes complejas
Proxy Control de acceso, lazy loading, caching, seguridad Smart pointers para ownership, RAII para cleanup, thread safety nativa Puede añadir latencia, debugging más complejo Lazy loading de imágenes, control de acceso, caching
Bridge Abstracción e implementación variables, plataformas múltiples Templates para implementaciones genéricas, zero-cost abstractions, type safety Aumenta complejidad de código, más clases que mantener Drivers multiplataforma, renderers gráficos, sistemas operativos
Composite Estructuras jerárquicas, árboles, menús, sistemas de archivos STL containers para estructuras eficientes, smart pointers para ownership, algoritmos STL Puede ser overkill para estructuras simples, memory overhead Sistemas de archivos, UI components, expresiones matemáticas
Flyweight Muchos objetos similares, optimización de memoria std::shared_ptr para sharing eficiente, thread safety con mutex, zero-cost abstractions Aumenta complejidad de código, debugging más difícil Tipos de caracteres, tipos de archivo, configuración de UI

Ejemplos prácticos de selección de patrones

Caso 1: Integración con API externa (Adapter vs Facade)

// ❌ Sin patrones - código acoplado
class PaymentProcessor {
public:
    void processPayment(const std::string& api_url, double amount) {
        // Código específico para cada API
        if (api_url.find("stripe") != std::string::npos) {
            // Lógica específica de Stripe
        } else if (api_url.find("paypal") != std::string::npos) {
            // Lógica específica de PayPal
        }
    }
};

// ✅ Con Adapter - cada API tiene su adapter
class PaymentProvider {
public:
    virtual nlohmann::json processPayment(double amount, const std::string& token) = 0;
};

class StripeAdapter : public PaymentProvider {
public:
    nlohmann::json processPayment(double amount, const std::string& token) override {
        // Adaptar a interfaz de Stripe
        return stripe_api_.charge(amount * 100, token);
    }
private:
    StripeAPI stripe_api_;
};

class PayPalAdapter : public PaymentProvider {
public:
    nlohmann::json processPayment(double amount, const std::string& token) override {
        // Adaptar a interfaz de PayPal
        return paypal_api_.process(amount, token);
    }
private:
    PayPalAPI paypal_api_;
};

Caso 2: Sistema de logging extensible (Decorator)

// ✅ Decorator para logging flexible
class Logger {
public:
    virtual void log(const std::string& message) = 0;
    virtual ~Logger() = default;
};

class ConsoleLogger : public Logger {
public:
    void log(const std::string& message) override {
        std::cout << "[CONSOLE] " << message << std::endl;
    }
};

class FileLogger : public Logger {
public:
    explicit FileLogger(std::unique_ptr<Logger> logger)
        : logger_(std::move(logger)) {}

    void log(const std::string& message) override {
        // Log a archivo
        std::ofstream file("app.log", std::ios::app);
        file << "[FILE] " << message << std::endl;

        // Continuar cadena de responsabilidad
        if (logger_) {
            logger_->log(message);
        }
    }

private:
    std::unique_ptr<Logger> logger_;
};

class TimestampLogger : public Logger {
public:
    explicit TimestampLogger(std::unique_ptr<Logger> logger)
        : logger_(std::move(logger)) {}

    void log(const std::string& message) override {
        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);

        std::stringstream ss;
        ss << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S") << "] " << message;

        if (logger_) {
            logger_->log(ss.str());
        }
    }

private:
    std::unique_ptr<Logger> logger_;
};

Caso 3: Optimización de memoria (Flyweight vs Proxy)

// ✅ Flyweight para caracteres de texto
class CharacterType {
public:
    CharacterType(char character, const std::string& font, int size, const std::string& color)
        : character_(character), font_(font), size_(size), color_(color) {}

    void display(int x, int y) const {
        std::cout << "Dibujando '" << character_ << "' en (" << x << "," << y
                  << ") con fuente " << font_ << ", tamaño " << size_
                  << ", color " << color_ << std::endl;
    }

private:
    char character_;
    std::string font_;
    int size_;
    std::string color_;
};

class CharacterFactory {
public:
    std::shared_ptr<CharacterType> getCharacter(char c, const std::string& font, int size, const std::string& color) {
        CharacterKey key{c, font, size, color};

        std::lock_guard<std::mutex> lock(mutex_);
        auto it = flyweights_.find(key);
        if (it == flyweights_.end()) {
            auto flyweight = std::make_shared<CharacterType>(c, font, size, color);
            flyweights_[key] = flyweight;
            return flyweight;
        }
        return it->second;
    }

private:
    struct CharacterKey {
        char character;
        std::string font;
        int size;
        std::string color;

        bool operator==(const CharacterKey& other) const {
            return character == other.character && font == other.font &&
                   size == other.size && color == other.color;
        }
    };

    struct CharacterKeyHash {
        std::size_t operator()(const CharacterKey& key) const {
            return std::hash<char>()(key.character) ^
                   std::hash<std::string>()(key.font) ^
                   std::hash<int>()(key.size) ^
                   std::hash<std::string>()(key.color);
        }
    };

    std::unordered_map<CharacterKey, std::shared_ptr<CharacterType>, CharacterKeyHash> flyweights_;
    std::mutex mutex_;
};

Consideraciones específicas de C++ para cada patrón

Adapter en C++

  • Templates: Permiten adapters type-safe sin overhead de runtime
  • Multiple Inheritance: Soporte nativo para class adapters
  • Smart Pointers: Gestión automática de memoria para adapters complejos
  • Exception Safety: Control robusto de errores en adaptaciones

Decorator en C++

  • RAII: Limpieza automática de recursos decorados
  • Move Semantics: Transferencia eficiente de ownership
  • Templates: Decorators genéricos y reutilizables
  • Zero-Cost Abstractions: No hay overhead cuando se implementa correctamente

Facade en C++

  • Resource Management: RAII para inicialización y cleanup automático
  • Smart Pointers: Ownership claro de subsistemas
  • Templates: Facades genéricos para diferentes tipos de subsistemas
  • Exception Safety: Propagación controlada de errores

Proxy en C++

  • Lazy Loading: Implementación eficiente con smart pointers
  • Thread Safety: Soporte nativo para proxies concurrentes
  • Memory Management: Prevención de leaks con RAII
  • Performance: Inlining de métodos proxy cuando es apropiado

Bridge en C++

  • Template Specialization: Implementaciones específicas para diferentes tipos
  • Type Safety: Verificación en tiempo de compilación
  • Zero-Cost Abstractions: No hay overhead de runtime
  • Multiple Inheritance: Soporte nativo para abstracciones complejas

Composite en C++

  • STL Integration: Uso natural de std::vector, std::list para estructuras
  • Smart Pointers: Gestión automática de memoria para árboles
  • Algorithms: Uso de algoritmos STL para recorrer estructuras
  • Type Safety: Interfaces fuertemente tipadas

Flyweight en C++

  • Shared Pointers: std::shared_ptr para sharing eficiente
  • Thread Safety: Mutex para acceso concurrente a flyweights
  • Memory Efficiency: Reducción significativa del uso de memoria
  • Type Safety: Verificación en tiempo de compilación

Guía de decisión rápida

Usa Adapter cuando:

  • Necesites integrar código legacy
  • Trabajes con APIs externas incompatibles
  • Quieras cambiar interfaces sin modificar código existente

Usa Decorator cuando:

  • Necesites añadir funcionalidades opcionales
  • Quieras logging, caching o validación
  • Prefieras composición sobre herencia

Usa Facade cuando:

  • Tengas subsistemas complejos
  • Quieras simplificar interfaces
  • Necesites inicialización coordinada de componentes

Usa Proxy cuando:

  • Quieras lazy loading
  • Necesites control de acceso
  • Quieras caching transparente

Usa Bridge cuando:

  • Tengas abstracciones e implementaciones variables
  • Quieras independencia entre ambas
  • Desarrolles para múltiples plataformas

Usa Composite cuando:

  • Tengas estructuras jerárquicas
  • Quieras tratar objetos individuales y grupos uniformemente
  • Construyas árboles o estructuras anidadas

Usa Flyweight cuando:

  • Tengas muchos objetos similares
  • Quieras optimizar uso de memoria
  • Puedas separar estado intrínseco y extrínseco

Paso 10: Proyecto práctico - Sistema de archivos

Vamos a crear un sistema de archivos completo que utilice múltiples patrones estructurales, aprovechando las fortalezas de C++ como smart pointers, STL containers, RAII y templates.

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <algorithm>
#include <filesystem>
#include <chrono>
#include <functional>

// Flyweight para tipos de archivo
class FileType {
public:
    FileType(const std::string& extension, const std::string& description, const std::string& icon)
        : extension_(extension), description_(description), icon_(icon) {}

    std::string getInfo() const {
        return icon_ + " " + description_ + " (." + extension_ + ")";
    }

    std::string getExtension() const { return extension_; }
    std::string getDescription() const { return description_; }
    std::string getIcon() const { return icon_; }

private:
    std::string extension_;
    std::string description_;
    std::string icon_;
};

// Fábrica singleton thread-safe para tipos de archivo (Flyweight Factory)
class FileTypeFactory {
public:
    static FileTypeFactory& getInstance() {
        static FileTypeFactory instance;
        return instance;
    }

    std::shared_ptr<FileType> getFileType(const std::string& extension) {
        std::string ext_lower = extension;
        std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);

        std::lock_guard<std::mutex> lock(mutex_);

        auto it = file_types_.find(ext_lower);
        if (it == file_types_.end()) {
            // Crear nuevo tipo de archivo
            std::string description = "Archivo " + extension;
            std::string icon = "📄";

            // Asignar iconos según extensión
            if (ext_lower == "txt") {
                description = "Archivo de texto";
                icon = "📄";
            } else if (ext_lower == "py" || ext_lower == "cpp" || ext_lower == "java") {
                description = "Archivo de código fuente";
                icon = "💻";
            } else if (ext_lower == "jpg" || ext_lower == "png" || ext_lower == "gif") {
                description = "Archivo de imagen";
                icon = "🖼️";
            } else if (ext_lower == "pdf") {
                description = "Documento PDF";
                icon = "📕";
            } else if (ext_lower == "mp3" || ext_lower == "wav") {
                description = "Archivo de audio";
                icon = "🎵";
            } else if (ext_lower == "mp4" || ext_lower == "avi") {
                description = "Archivo de video";
                icon = "🎬";
            }

            auto file_type = std::make_shared<FileType>(ext_lower, description, icon);
            file_types_[ext_lower] = file_type;
            std::cout << "Creando nuevo tipo de archivo: " << ext_lower << std::endl;
            return file_type;
        } else {
            std::cout << "Reutilizando tipo de archivo: " << ext_lower << std::endl;
            return it->second;
        }
    }

    size_t getFileTypeCount() const {
        return file_types_.size();
    }

private:
    FileTypeFactory() = default;
    std::unordered_map<std::string, std::shared_ptr<FileType>> file_types_;
    std::mutex mutex_;
};

// Interfaz base para elementos del sistema de archivos (Component)
class FileSystemElement {
public:
    explicit FileSystemElement(const std::string& name) : name_(name) {}
    virtual ~FileSystemElement() = default;

    // Operaciones comunes
    virtual size_t getSize() const = 0;
    virtual void display(int indent = 0) const = 0;
    virtual std::string getName() const { return name_; }

    // Método para verificar si es un directorio (útil para sorting)
    virtual bool isDirectory() const = 0;

protected:
    std::string name_;
};

// Hoja: Archivo (Leaf)
class File : public FileSystemElement {
public:
    File(const std::string& name, size_t size_in_bytes)
        : FileSystemElement(name), size_in_bytes_(size_in_bytes) {

        size_t dot_pos = name_.find_last_of('.');
        if (dot_pos != std::string::npos) {
            std::string extension = name_.substr(dot_pos + 1);
            file_type_ = FileTypeFactory::getInstance().getFileType(extension);
        } else {
            file_type_ = FileTypeFactory::getInstance().getFileType("");
        }
    }

    size_t getSize() const override {
        return size_in_bytes_;
    }

    void display(int indent) const override {
        std::string indentation(indent * 2, ' ');
        std::cout << indentation << file_type_->getInfo() << " - " << getName()
                  << " (" << (size_in_bytes_ / 1024) << " KB)" << std::endl;
    }

    bool isDirectory() const override {
        return false;
    }

    std::shared_ptr<FileType> getFileType() const { return file_type_; }

private:
    size_t size_in_bytes_;
    std::shared_ptr<FileType> file_type_;  // Flyweight compartido
};

// Composite: Directorio
class Directory : public FileSystemElement {
public:
    explicit Directory(const std::string& name) : FileSystemElement(name) {}

    // Gestión de elementos hijos
    void addElement(std::unique_ptr<FileSystemElement> element) {
        elements_.push_back(std::move(element));
        updateModificationTime();
    }

    bool removeElement(const std::string& name) {
        auto it = std::find_if(elements_.begin(), elements_.end(),
            [&name](const std::unique_ptr<FileSystemElement>& elem) {
                return elem->getName() == name;
            });

        if (it != elements_.end()) {
            elements_.erase(it);
            updateModificationTime();
            return true;
        }
        return false;
    }

    FileSystemElement* getElement(const std::string& name) {
        auto it = std::find_if(elements_.begin(), elements_.end(),
            [&name](const std::unique_ptr<FileSystemElement>& elem) {
                return elem->getName() == name;
            });

        return (it != elements_.end()) ? it->get() : nullptr;
    }

    size_t getSize() const override {
        size_t total_size = 0;
        for (const auto& element : elements_) {
            total_size += element->getSize();
        }
        return total_size;
    }

    void display(int indent = 0) const override {
        std::string indentation(indent * 2, ' ');
        std::cout << indentation << "📁 " << getName() << "/" << std::endl;

        // Ordenar elementos: directorios primero, luego archivos
        std::vector<std::pair<bool, const FileSystemElement*>> sorted_elements;
        for (const auto& element : elements_) {
            sorted_elements.emplace_back(element->isDirectory(), element.get());
        }

        std::sort(sorted_elements.begin(), sorted_elements.end(),
                  std::greater<std::pair<bool, const FileSystemElement*>>());

        for (const auto& [is_dir, element] : sorted_elements) {
            element->display(indent + 1);
        }
    }

    const std::vector<std::unique_ptr<FileSystemElement>>& getElements() const {
        return elements_;
    }

    bool isDirectory() const override {
        return true;
    }

private:
    void updateModificationTime() {
        // En una implementación real, actualizaría timestamp
        last_modified_ = std::chrono::system_clock::now();
    }

    std::vector<std::unique_ptr<FileSystemElement>> elements_;
    std::chrono::system_clock::time_point last_modified_;
};

// Adapter para sistema de archivos real
class FileSystemAdapter {
public:
    explicit FileSystemAdapter(const std::string& base_path)
        : base_path_(base_path) {}

    std::unique_ptr<Directory> loadDirectory(const std::string& relative_path = "") {
        std::filesystem::path full_path = std::filesystem::path(base_path_) / relative_path;

        if (!std::filesystem::exists(full_path)) {
            throw std::runtime_error("Directorio no encontrado: " + full_path.string());
        }

        std::string dir_name = full_path.filename().string();
        if (dir_name.empty()) dir_name = "raiz";

        auto directory = std::make_unique<Directory>(dir_name);

        try {
            for (const auto& entry : std::filesystem::directory_iterator(full_path)) {
                if (std::filesystem::is_directory(entry)) {
                    auto subdirectory = loadDirectory((std::filesystem::path(relative_path) / entry.path().filename()).string());
                    directory->addElement(std::move(subdirectory));
                } else if (std::filesystem::is_regular_file(entry)) {
                    size_t file_size = std::filesystem::file_size(entry);
                    auto file = std::make_unique<File>(entry.path().filename().string(), file_size);
                    directory->addElement(std::move(file));
                }
            }
        } catch (const std::filesystem::filesystem_error& e) {
            std::cerr << "Error accediendo al sistema de archivos: " << e.what() << std::endl;
        }

        return directory;
    }

private:
    std::string base_path_;
};

// Facade para operaciones comunes del sistema de archivos
class FileSystemFacade {
public:
    explicit FileSystemFacade(const std::string& base_path)
        : adapter_(std::make_unique<FileSystemAdapter>(base_path))
        , root_(nullptr) {}

    Directory* loadSystem() {
        root_ = adapter_->loadDirectory();
        return root_.get();
    }

    std::vector<File*> searchFiles(const std::string& extension, Directory* directory = nullptr) {
        std::vector<File*> found_files;

        if (directory == nullptr) {
            directory = root_.get();
        }

        if (!directory) return found_files;

        std::function<void(Directory*)> searchInDirectory = [&](Directory* dir) {
            for (const auto& element : dir->getElements()) {
                if (element->isDirectory()) {
                    searchInDirectory(static_cast<Directory*>(element.get()));
                } else {
                    File* file = static_cast<File*>(element.get());
                    if (file->getFileType()->getExtension() == extension) {
                        found_files.push_back(file);
                    }
                }
            }
        };

        searchInDirectory(directory);
        return found_files;
    }

    struct FileSystemStats {
        size_t file_count;
        size_t directory_count;
        size_t total_size;
        std::unordered_map<std::string, int> file_types;
    };

    FileSystemStats getStatistics(Directory* directory = nullptr) {
        FileSystemStats stats{0, 0, 0, {}};

        if (directory == nullptr) {
            directory = root_.get();
        }

        if (!directory) return stats;

        std::function<void(Directory*)> countElements = [&](Directory* dir) {
            for (const auto& element : dir->getElements()) {
                if (element->isDirectory()) {
                    stats.directory_count++;
                    countElements(static_cast<Directory*>(element.get()));
                } else {
                    stats.file_count++;
                    stats.total_size += element->getSize();
                    File* file = static_cast<File*>(element.get());
                    stats.file_types[file->getFileType()->getExtension()]++;
                }
            }
        };

        countElements(directory);
        return stats;
    }

private:
    std::unique_ptr<FileSystemAdapter> adapter_;
    std::unique_ptr<Directory> root_;
};

// Demo del sistema completo
void demoFileSystem() {
    std::cout << "=== Creando estructura de archivos de ejemplo ===" << std::endl;

    // Crear estructura de ejemplo usando Composite
    auto root = std::make_unique<Directory>("MiProyecto");

    // Crear subdirectorios
    auto src = std::make_unique<Directory>("src");
    auto docs = std::make_unique<Directory>("docs");
    auto assets = std::make_unique<Directory>("assets");

    // Agregar archivos con diferentes tipos (demostrando Flyweight)
    src->addElement(std::make_unique<File>("main.cpp", 15 * 1024));
    src->addElement(std::make_unique<File>("utils.cpp", 8 * 1024));
    src->addElement(std::make_unique<File>("config.py", 5 * 1024));  // Reutilizará tipo .py

    docs->addElement(std::make_unique<File>("README.md", 3 * 1024));
    docs->addElement(std::make_unique<File>("manual.pdf", 250 * 1024));

    assets->addElement(std::make_unique<File>("logo.jpg", 1200 * 1024));
    assets->addElement(std::make_unique<File>("icon.png", 45 * 1024));
    assets->addElement(std::make_unique<File>("background.mp3", 3500 * 1024));

    root->addElement(std::move(src));
    root->addElement(std::move(docs));
    root->addElement(std::move(assets));

    // Usar Facade para operaciones
    FileSystemFacade facade("");
    facade.loadSystem();  // En este ejemplo usamos la estructura creada manualmente

    std::cout << std::endl << "=== Estructura del Sistema de Archivos ===" << std::endl;
    root->display();

    std::cout << std::endl << "=== Estadísticas ===" << std::endl;
    auto stats = facade.getStatistics(root.get());
    std::cout << "Archivos: " << stats.file_count << std::endl;
    std::cout << "Directorios: " << stats.directory_count << std::endl;
    std::cout << "Tamaño total: " << (stats.total_size / 1024) << " KB" << std::endl;
    std::cout << "Tipos de archivo:" << std::endl;
    for (const auto& [ext, count] : stats.file_types) {
        std::cout << "  " << ext << ": " << count << std::endl;
    }

    std::cout << std::endl << "=== Búsqueda de archivos C++ ===" << std::endl;
    auto cpp_files = facade.searchFiles("cpp", root.get());
    for (const auto& file : cpp_files) {
        std::cout << "  💻 " << file->getName() << std::endl;
    }

    std::cout << std::endl << "=== Demostración de Flyweight ===" << std::endl;
    std::cout << "Total de tipos de archivo únicos: " << FileTypeFactory::getInstance().getFileTypeCount() << std::endl;
    std::cout << "Memoria ahorrada: " << (stats.file_count - FileTypeFactory::getInstance().getFileTypeCount())
              << " objetos FileType" << std::endl;

    // Demostrar operaciones del Composite
    std::cout << std::endl << "=== Operaciones del Composite ===" << std::endl;

    // Buscar archivos Python usando algoritmos STL
    std::cout << "Archivos Python:" << std::endl;
    std::function<void(Directory*, int)> findPythonFiles = [&](Directory* dir, int indent) {
        std::string indentation(indent * 2, ' ');
        for (const auto& element : dir->getElements()) {
            if (element->isDirectory()) {
                findPythonFiles(static_cast<Directory*>(element.get()), indent + 1);
            } else {
                File* file = static_cast<File*>(element.get());
                if (file->getFileType()->getExtension() == "py") {
                    std::cout << indentation << "🐍 " << file->getName() << std::endl;
                }
            }
        }
    };

    findPythonFiles(root.get(), 0);
}

// Demo con sistema de archivos real (opcional)
void demoRealFileSystem() {
    try {
        std::cout << std::endl << "=== Demo con sistema de archivos real ===" << std::endl;

        // Adaptar el directorio actual como ejemplo
        FileSystemAdapter adapter(std::filesystem::current_path().string());
        auto real_directory = adapter.loadDirectory();

        FileSystemFacade real_facade(std::filesystem::current_path().string());
        real_facade.loadSystem();

        std::cout << "Directorio actual:" << std::endl;
        real_directory->display();

        auto txt_files = real_facade.searchFiles("txt");
        std::cout << std::endl << "Archivos .txt encontrados: " << txt_files.size() << std::endl;

    } catch (const std::exception& e) {
        std::cout << "No se pudo acceder al sistema de archivos real: " << e.what() << std::endl;
        std::cout << "Ejecutando demo con estructura simulada..." << std::endl;
    }
}

int main() {
    std::cout << "=== Sistema de Archivos con Patrones Estructurales ===" << std::endl;

    demoFileSystem();
    demoRealFileSystem();

    return 0;
}

Análisis del proyecto

Este proyecto demuestra la integración de múltiples patrones estructurales en C++:

Flyweight

  • FileTypeFactory: Singleton thread-safe que comparte instancias de FileType
  • FileType: Estado intrínseco compartido entre múltiples archivos
  • std::shared_ptr: Para compartir eficientemente los tipos de archivo

Composite

  • FileSystemElement: Interfaz base para archivos y directorios
  • File: Hoja que representa archivos individuales
  • Directory: Composite que contiene otros elementos
  • Operaciones uniformes: getSize(), display(), isDirectory()

Adapter

  • FileSystemAdapter: Adapta la API de std::filesystem a nuestra interfaz
  • Conversión de tipos y manejo de errores del sistema de archivos real

Facade

  • FileSystemFacade: Proporciona interfaz simplificada para operaciones complejas
  • Oculta la complejidad de búsqueda, estadísticas y navegación

Ventajas de la implementación en C++

  • Memory Safety: Smart pointers previenen leaks y dangling pointers
  • Thread Safety: Mutex en la fábrica de flyweights para acceso concurrente
  • Performance: STL algorithms para búsqueda y ordenamiento eficiente
  • Type Safety: Templates y verificación en tiempo de compilación
  • RAII: Limpieza automática de recursos
  • Zero-Cost Abstractions: No hay overhead innecesario
  • Exception Safety: Control robusto de errores del sistema de archivos

Características demostradas

  1. Composición de patrones: Los patrones trabajan juntos de manera natural
  2. Uso eficiente de memoria: Flyweight comparte tipos de archivo comunes
  3. Estructuras jerárquicas: Composite maneja árboles de archivos complejos
  4. Abstracción de complejidad: Facade simplifica operaciones del sistema
  5. Integración con STL: Uso natural de containers y algorithms modernos
  6. Thread safety: Consideraciones de concurrencia donde es necesario

Conclusión

¡Has dominado los patrones de diseño estructurales en C++! Estos patrones te permiten organizar y simplificar la estructura de tus aplicaciones, aprovechando las fortalezas únicas del lenguaje para crear software robusto, eficiente y mantenible.

Lo que has aprendido

  • Adapter: Conectar interfaces incompatibles con templates type-safe y herencia múltiple
  • Decorator: Añadir funcionalidades dinámicamente con herencia múltiple nativa y RAII
  • Facade: Simplificar subsistemas complejos con gestión automática de recursos
  • Proxy: Controlar acceso con smart pointers y thread safety
  • Bridge: Separar abstracciones de implementaciones con templates genéricos
  • Composite: Crear estructuras jerárquicas con STL containers y smart pointers
  • Flyweight: Compartir objetos eficientemente con std::shared_ptr y thread safety

Beneficios de aplicar estos patrones en C++

  • Type Safety: Verificación en tiempo de compilación con templates
  • Zero-Cost Abstractions: No hay overhead de runtime cuando se implementan correctamente
  • Memory Safety: Smart pointers previenen leaks y dangling pointers
  • Performance: Inlining y optimizaciones del compilador
  • Thread Safety: Soporte nativo para concurrencia
  • RAII: Gestión automática de recursos
  • STL Integration: Uso natural de containers y algorithms modernos

Proyecto práctico completado

El sistema de archivos que creamos demuestra la integración perfecta de múltiples patrones estructurales:

  • Flyweight para compartir tipos de archivo eficientemente
  • Composite para estructuras jerárquicas de archivos y directorios
  • Adapter para integrar con el sistema de archivos real (std::filesystem)
  • Facade para proporcionar una interfaz simplificada y unificada

Este proyecto muestra cómo los patrones estructurales trabajan juntos de manera natural en C++, aprovechando las características modernas del lenguaje para crear código robusto, eficiente y mantenible.

Consejos para aplicar estos patrones

  1. Empieza simple: No apliques todos los patrones desde el inicio
  2. Refactoriza incrementalmente: Mejora el código existente gradualmente
  3. Entiende el contexto: Aplica patrones según las necesidades específicas
  4. Mide el impacto: Evalúa si los beneficios justifican la complejidad añadida
  5. Mantén el balance: Encuentra el equilibrio entre patrones y simplicidad
  6. Documenta decisiones: Explica por qué elegiste ciertos patrones
  7. Prueba exhaustivamente: Asegúrate de que los patrones funcionen correctamente
  8. Considera el rendimiento: Algunos patrones pueden afectar el performance

Practica aplicando estos patrones en proyectos reales y combina diferentes patrones según las necesidades específicas de tu aplicación.

Para más tutoriales sobre patrones de diseño estructurales avanzados y aplicaciones en C++, visita nuestra sección de tutoriales.


¡Sigue practicando y aplicando estos patrones en proyectos reales!


💡 Tip Importante

📝 Mejores Prácticas en Patrones Estructurales con C++

  • Elige el patrón adecuado: Analiza la estructura del problema antes de seleccionar un patrón
  • Aprovecha las fortalezas de C++: Usa templates, smart pointers, RAII y STL
  • Principio de responsabilidad única: Cada clase debe tener una sola razón para cambiar
  • Principio abierto/cerrado: Los patrones deben estar abiertos a extensión pero cerrados a modificación
  • Composición sobre herencia: Prefiere composición cuando sea posible
  • Type Safety: Usa templates y verificación en tiempo de compilación
  • Memory Safety: Implementa RAII y usa smart pointers apropiadamente
  • Thread Safety: Considera concurrencia cuando sea necesario
  • Mantén la simplicidad: No uses patrones complejos para problemas simples
  • Documenta tus decisiones: Explica por qué elegiste ciertos patrones en tu código
  • Prueba tus estructuras: Asegúrate de que tus patrones funcionen correctamente
  • Considera el rendimiento: Algunos patrones pueden afectar el rendimiento
  • Usa STL apropiadamente: Integra patrones con containers y algorithms estándar

📚 Recursos Recomendados para C++:

🎯 Aplicaciones prácticas en C++:

  • Adapter: Integración con APIs de C, bibliotecas legacy, diferentes frameworks
  • Decorator: Middleware, logging, caching, validación de datos, streams
  • Facade: Compiladores, sistemas de archivos, redes complejas, APIs
  • Proxy: Lazy loading de recursos, control de acceso, caching, virtual filesystems
  • Bridge: Drivers multiplataforma, renderers gráficos, sistemas operativos
  • Composite: UI components, expresiones matemáticas, sistemas de archivos
  • Flyweight: Tipos de caracteres, tipos de archivo, configuración de UI, pools de objetos

¡Estos patrones y recursos te ayudarán a crear software C++ con mejor estructura y organización!

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 Creacionales en C++
Patrones de Diseño

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.

José Elías Romero Guanipa
03 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