Patrones de Diseño Estructurales en C++
Aprende patrones de diseño estructurales como Adapter, Decorator, Facade y Proxy con ejemplos prácticos en C++, aprovechando las fortalezas del lenguaje para estructuras robustas y eficientes.
¡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_ptrystd::weak_ptrfacilitan 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?
- Paso 2: Adapter - Conectar interfaces incompatibles
- Paso 3: Decorator - Añadir funcionalidad dinámicamente
- Paso 4: Facade - Interfaz simplificada
- Paso 5: Proxy - Control de acceso
- Paso 6: Bridge - Separar abstracción e implementación
- Paso 7: Composite - Estructuras de árbol
- Paso 8: Flyweight - Compartir objetos eficientemente
- Paso 9: Comparación y selección de patrones
- Paso 10: Proyecto práctico - Sistema de archivos
- Conclusión
- 💡 Tip Importante
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_ptrpara 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::listpara 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_ptrpara 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 deFileTypeFileType: Estado intrínseco compartido entre múltiples archivosstd::shared_ptr: Para compartir eficientemente los tipos de archivo
Composite
FileSystemElement: Interfaz base para archivos y directoriosFile: Hoja que representa archivos individualesDirectory: Composite que contiene otros elementos- Operaciones uniformes:
getSize(),display(),isDirectory()
Adapter
FileSystemAdapter: Adapta la API destd::filesystema 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
- Composición de patrones: Los patrones trabajan juntos de manera natural
- Uso eficiente de memoria: Flyweight comparte tipos de archivo comunes
- Estructuras jerárquicas: Composite maneja árboles de archivos complejos
- Abstracción de complejidad: Facade simplifica operaciones del sistema
- Integración con STL: Uso natural de containers y algorithms modernos
- 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_ptry 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
- Empieza simple: No apliques todos los patrones desde el inicio
- Refactoriza incrementalmente: Mejora el código existente gradualmente
- Entiende el contexto: Aplica patrones según las necesidades específicas
- Mide el impacto: Evalúa si los beneficios justifican la complejidad añadida
- Mantén el balance: Encuentra el equilibrio entre patrones y simplicidad
- Documenta decisiones: Explica por qué elegiste ciertos patrones
- Prueba exhaustivamente: Asegúrate de que los patrones funcionen correctamente
- 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++:
- Design Patterns in Modern C++ - Dmitri Nesteruk
- Modern C++ Design - Andrei Alexandrescu
- Effective Modern C++ - Scott Meyers
- C++ Core Guidelines - Bjarne Stroustrup y Herb Sutter
- Refactoring.Guru - Structural Patterns - Ejemplos interactivos
- Design Patterns: Elements of Reusable Object-Oriented Software - Gang of Four
🎯 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!
No hay comentarios aún
Sé el primero en comentar este tutorial.