Patrones de Diseño de Comportamiento en C++
Aprende patrones de diseño de comportamiento como Observer, Strategy, Command, State, Template Method, Iterator, Mediator y Memento con ejemplos prácticos en C++. Descubre cómo implementar estos patrones aprovechando las fortalezas del lenguaje C++ como type safety, performance y memory management.
¡Domina los patrones de diseño de comportamiento en C++! En este tutorial especializado te guiaré paso a paso para que aprendas los patrones fundamentales de comportamiento, incluyendo Observer, Strategy, Command, State, Template Method, Iterator, Mediator y Memento, con ejemplos prácticos y casos de uso reales implementados en C++ moderno.
Objetivo: Aprender los patrones de diseño de comportamiento más importantes, sus implementaciones en C++, ventajas, desventajas y cuándo aplicarlos para gestionar algoritmos, estados y comunicaciones entre objetos, aprovechando las fortalezas únicas del lenguaje C++.
¿Por qué C++ para patrones de comportamiento?
C++ es especialmente adecuado para implementar patrones de comportamiento porque ofrece un control y eficiencia que otros lenguajes no pueden igualar:
- Type Safety y Zero-cost Abstractions: El sistema de tipos fuerte de C++ detecta errores en tiempo de compilación, y los patrones bien diseñados no añaden overhead en runtime gracias al inlining y optimizaciones del compilador
- Performance Superior: Los patrones se ejecutan con overhead mínimo, ideal para aplicaciones críticas donde cada microsegundo cuenta
- Memory Management Avanzado: Control preciso sobre la memoria con RAII (Resource Acquisition Is Initialization) y smart pointers, permitiendo implementar patrones como Memento y Command de manera segura y eficiente
- Multi-threading Nativo: Soporte integrado para concurrencia en patrones como Observer, con herramientas como mutex, atomic y memory models para thread safety
- Templates Metaprogramming: Permiten implementar patrones genéricos y reutilizables, con type safety en tiempo de compilación y optimizaciones automáticas
- Encapsulation Estricta: Control total sobre el acceso a miembros de clase, permitiendo implementar patrones como State y Strategy con verdadera encapsulación
- STL Integration: Muchos patrones ya están implementados eficientemente en la Standard Template Library, y los patrones personalizados pueden integrarse perfectamente
- Move Semantics y RAII: Permiten implementar patrones con máxima eficiencia, transfiriendo ownership de recursos sin copias innecesarias
Índice
- Paso 1: ¿Qué son los patrones de comportamiento?
- Paso 2: Observer - Notificaciones de cambios
- Paso 3: Strategy - Algoritmos intercambiables
- Paso 4: Command - Encapsular operaciones
- Paso 5: State - Cambio de comportamiento por estado
- Paso 6: Template Method - Esqueleto de algoritmo
- Paso 7: Iterator - Recorrer colecciones
- Paso 8: Mediator - Comunicación centralizada
- Paso 9: Memento - Guardar y restaurar estado
- Paso 10: Comparación y selección de patrones
- Paso 11: Proyecto práctico - Editor de texto
- Conclusión
- 💡 Tip Importante
Paso 1: ¿Qué son los patrones de comportamiento?
Los patrones de diseño de comportamiento se centran en la comunicación entre objetos y la asignación de responsabilidades entre ellos. Estos patrones ayudan a definir cómo los objetos interactúan y se distribuyen las responsabilidades.
¿Por qué son importantes?
- Flexibilidad: Permiten cambiar el comportamiento en tiempo de ejecución
- Desacoplamiento: Reducen las dependencias entre objetos
- Reutilización: Facilitan la composición de comportamientos
- Mantenimiento: Hacen el código más comprensible y modificable
Clasificación de patrones de comportamiento
| Patrón | Propósito | Complejidad |
|---|---|---|
| Observer | Notificaciones de cambios | Media |
| Strategy | Algoritmos intercambiables | Baja |
| Command | Encapsular operaciones | Media |
| State | Cambio por estado | Media |
| Template Method | Esqueleto de algoritmo | Baja |
| Iterator | Recorrer colecciones | Baja |
| Mediator | Comunicación centralizada | Alta |
| Memento | Guardar/restaurar estado | Media |
Paso 2: Observer - Notificaciones de cambios
El patrón Observer define una dependencia de uno a muchos entre objetos, de modo que cuando un objeto cambia su estado, todos sus dependientes son notificados y actualizados automáticamente.
Implementación básica
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// Interfaz abstracta para observadores
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& estado) = 0;
};
// Sujeto que mantiene una lista de observadores
class Subject {
private:
std::vector<std::shared_ptr<Observer>> observers_;
std::string estado_;
public:
void attach(const std::shared_ptr<Observer>& observer) {
observers_.push_back(observer);
}
void detach(const std::shared_ptr<Observer>& observer) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), observer),
observers_.end()
);
}
void notify() {
for (const auto& observer : observers_) {
observer->update(estado_);
}
}
void setEstado(const std::string& nuevo_estado) {
estado_ = nuevo_estado;
notify();
}
const std::string& getEstado() const {
return estado_;
}
};
// Observadores concretos
class ConcreteObserverA : public Observer {
public:
void update(const std::string& estado) override {
std::cout << "Observador A: Estado cambiado a " << estado << std::endl;
}
};
class ConcreteObserverB : public Observer {
public:
void update(const std::string& estado) override {
std::cout << "Observador B: Recibido cambio - " << estado << std::endl;
}
};
// Uso
int main() {
auto subject = std::make_shared<Subject>();
auto observer_a = std::make_shared<ConcreteObserverA>();
auto observer_b = std::make_shared<ConcreteObserverB>();
subject->attach(observer_a);
subject->attach(observer_b);
subject->setEstado("Nuevo estado");
subject->setEstado("Otro estado");
subject->detach(observer_b);
subject->setEstado("Estado sin observador B");
return 0;
}
Observer para sistema de notificaciones
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// Sistema de notificaciones que hereda de Subject
class NotificationSystem : public Subject {
private:
std::vector<std::string> messages_;
public:
void sendMessage(const std::string& message) {
messages_.push_back(message);
setEstado("Nuevo mensaje: " + message);
}
const std::vector<std::string>& getMessages() const {
return messages_;
}
};
// Cliente de email
class EmailClient : public Observer {
private:
std::string email_;
public:
EmailClient(const std::string& email) : email_(email) {}
void update(const std::string& estado) override {
// En una implementación real, aquí se enviaría el email
std::cout << "Enviando email a " << email_ << ": " << estado << std::endl;
}
};
// Cliente SMS
class SMSClient : public Observer {
private:
std::string phone_;
public:
SMSClient(const std::string& phone) : phone_(phone) {}
void update(const std::string& estado) override {
// En una implementación real, aquí se enviaría el SMS
std::cout << "Enviando SMS a " << phone_ << ": " << estado << std::endl;
}
};
// Uso
int main() {
auto notification_system = std::make_shared<NotificationSystem>();
auto email_client1 = std::make_shared<EmailClient>("[email protected]");
auto sms_client = std::make_shared<SMSClient>("+123456789");
auto email_client2 = std::make_shared<EmailClient>("[email protected]");
notification_system->attach(email_client1);
notification_system->attach(sms_client);
notification_system->attach(email_client2);
notification_system->sendMessage("Sistema iniciado");
notification_system->sendMessage("Backup completado");
notification_system->sendMessage("Error crítico detectado");
return 0;
}
Paso 3: Strategy - Algoritmos intercambiables
El patrón Strategy define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo usan.
Implementación básica
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
// Interfaz abstracta para estrategias de ordenamiento
template <typename T>
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual std::vector<T> sort(const std::vector<T>& data) const = 0;
virtual std::string getName() const = 0;
};
// Estrategia de ordenamiento burbuja
template <typename T>
class BubbleSort : public SortStrategy<T> {
public:
std::vector<T> sort(const std::vector<T>& data) const override {
std::vector<T> result = data; // Copia para no modificar original
size_t n = result.size();
for (size_t i = 0; i < n - 1; ++i) {
for (size_t j = 0; j < n - i - 1; ++j) {
if (result[j] > result[j + 1]) {
std::swap(result[j], result[j + 1]);
}
}
}
return result;
}
std::string getName() const override {
return "Bubble Sort";
}
};
// Estrategia de ordenamiento rápido (quicksort)
template <typename T>
class QuickSort : public SortStrategy<T> {
private:
std::vector<T> quickSortRecursive(std::vector<T> data) const {
if (data.size() <= 1) {
return data;
}
T pivot = data[data.size() / 2];
std::vector<T> left, middle, right;
for (const T& element : data) {
if (element < pivot) {
left.push_back(element);
} else if (element == pivot) {
middle.push_back(element);
} else {
right.push_back(element);
}
}
std::vector<T> sorted_left = quickSortRecursive(left);
std::vector<T> sorted_right = quickSortRecursive(right);
sorted_left.insert(sorted_left.end(), middle.begin(), middle.end());
sorted_left.insert(sorted_left.end(), sorted_right.begin(), sorted_right.end());
return sorted_left;
}
public:
std::vector<T> sort(const std::vector<T>& data) const override {
return quickSortRecursive(data);
}
std::string getName() const override {
return "Quick Sort";
}
};
// Estrategia de ordenamiento por selección
template <typename T>
class SelectionSort : public SortStrategy<T> {
public:
std::vector<T> sort(const std::vector<T>& data) const override {
std::vector<T> result = data;
size_t n = result.size();
for (size_t i = 0; i < n - 1; ++i) {
size_t min_idx = i;
for (size_t j = i + 1; j < n; ++j) {
if (result[j] < result[min_idx]) {
min_idx = j;
}
}
if (min_idx != i) {
std::swap(result[i], result[min_idx]);
}
}
return result;
}
std::string getName() const override {
return "Selection Sort";
}
};
// Contexto que usa la estrategia
template <typename T>
class SortContext {
private:
std::unique_ptr<SortStrategy<T>> strategy_;
public:
void setStrategy(std::unique_ptr<SortStrategy<T>> strategy) {
strategy_ = std::move(strategy);
}
std::vector<T> executeSort(const std::vector<T>& data) const {
if (!strategy_) {
throw std::runtime_error("Estrategia no establecida");
}
std::cout << "Usando estrategia: " << strategy_->getName() << std::endl;
return strategy_->sort(data);
}
};
// Uso
int main() {
std::vector<int> data = {64, 34, 25, 12, 22, 11, 90};
SortContext<int> context;
std::cout << "Datos originales: ";
for (int num : data) {
std::cout << num << " ";
}
std::cout << std::endl;
// Usar burbuja
context.setStrategy(std::make_unique<BubbleSort<int>>());
auto result = context.executeSort(data);
std::cout << "Burbuja: ";
for (int num : result) {
std::cout << num << " ";
}
std::cout << std::endl;
// Cambiar a quicksort
context.setStrategy(std::make_unique<QuickSort<int>>());
result = context.executeSort(data);
std::cout << "Quicksort: ";
for (int num : result) {
std::cout << num << " ";
}
std::cout << std::endl;
// Cambiar a selección
context.setStrategy(std::make_unique<SelectionSort<int>>());
result = context.executeSort(data);
std::cout << "Selección: ";
for (int num : result) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Strategy para procesamiento de pagos
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <iomanip>
// Interfaz abstracta para estrategias de pago
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual std::string processPayment(double amount) const = 0;
virtual std::string getName() const = 0;
};
// Estrategia de pago con tarjeta de crédito
class CreditCardPayment : public PaymentStrategy {
public:
std::string processPayment(double amount) const override {
return "Procesando $" + std::to_string(amount) + " con tarjeta de crédito";
}
std::string getName() const override {
return "Tarjeta de Crédito";
}
};
// Estrategia de pago con PayPal
class PayPalPayment : public PaymentStrategy {
public:
std::string processPayment(double amount) const override {
return "Procesando $" + std::to_string(amount) + " con PayPal";
}
std::string getName() const override {
return "PayPal";
}
};
// Estrategia de pago con Bitcoin
class BitcoinPayment : public PaymentStrategy {
public:
std::string processPayment(double amount) const override {
return "Procesando $" + std::to_string(amount) + " con Bitcoin";
}
std::string getName() const override {
return "Bitcoin";
}
};
// Carrito de compras que usa la estrategia
class ShoppingCart {
private:
std::vector<std::pair<std::string, double>> items_;
std::unique_ptr<PaymentStrategy> payment_strategy_;
public:
void addItem(const std::string& item, double price) {
items_.emplace_back(item, price);
}
double calculateTotal() const {
double total = 0.0;
for (const auto& item : items_) {
total += item.second;
}
return total;
}
void setPaymentMethod(std::unique_ptr<PaymentStrategy> strategy) {
payment_strategy_ = std::move(strategy);
}
std::string checkout() const {
if (!payment_strategy_) {
throw std::runtime_error("Método de pago no seleccionado");
}
double total = calculateTotal();
return payment_strategy_->processPayment(total);
}
void displayCart() const {
std::cout << "Carrito de compras:" << std::endl;
for (const auto& item : items_) {
std::cout << " - " << item.first << ": $" << std::fixed
<< std::setprecision(2) << item.second << std::endl;
}
std::cout << "Total: $" << std::fixed << std::setprecision(2)
<< calculateTotal() << std::endl;
}
};
// Uso
int main() {
ShoppingCart cart;
cart.addItem("Laptop", 1200.00);
cart.addItem("Mouse", 45.99);
cart.addItem("Teclado", 89.99);
cart.displayCart();
// Procesar con diferentes métodos de pago
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
std::cout << cart.checkout() << std::endl;
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
std::cout << cart.checkout() << std::endl;
cart.setPaymentMethod(std::make_unique<BitcoinPayment>());
std::cout << cart.checkout() << std::endl;
return 0;
}
Paso 4: Command - Encapsular operaciones
El patrón Command encapsula una solicitud como un objeto, permitiendo parametrizar clientes con diferentes solicitudes, encolar o registrar solicitudes, y soportar operaciones que pueden ser deshechas.
Implementación básica
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <functional>
// Interfaz abstracta para comandos
class Command {
public:
virtual ~Command() = default;
virtual std::string execute() = 0;
virtual std::string undo() = 0;
};
// Receptor que realiza las acciones
class Receiver {
private:
std::string estado_;
public:
Receiver() : estado_("Estado inicial") {}
std::string specialAction(const std::string& message) {
estado_ = "Estado después de: " + message;
return "Acción ejecutada: " + message;
}
std::string anotherAction(int number) {
estado_ = "Estado numérico: " + std::to_string(number);
return "Número procesado: " + std::to_string(number);
}
const std::string& getEstado() const {
return estado_;
}
void setEstado(const std::string& estado) {
estado_ = estado;
}
};
// Comando concreto
class ConcreteCommand : public Command {
private:
std::shared_ptr<Receiver> receiver_;
std::string action_;
std::string message_param_;
int int_param_;
std::string estado_anterior_;
bool has_message_param_;
bool has_int_param_;
public:
ConcreteCommand(std::shared_ptr<Receiver> receiver, const std::string& action)
: receiver_(receiver), action_(action), has_message_param_(false), has_int_param_(false) {}
ConcreteCommand(std::shared_ptr<Receiver> receiver, const std::string& action,
const std::string& message)
: receiver_(receiver), action_(action), message_param_(message),
has_message_param_(true), has_int_param_(false) {}
ConcreteCommand(std::shared_ptr<Receiver> receiver, const std::string& action, int number)
: receiver_(receiver), action_(action), int_param_(number),
has_message_param_(false), has_int_param_(true) {}
std::string execute() override {
// Guardar estado anterior para poder deshacer
estado_anterior_ = receiver_->getEstado();
if (action_ == "specialAction") {
return receiver_->specialAction(message_param_);
} else if (action_ == "anotherAction") {
return receiver_->anotherAction(int_param_);
}
return "Acción desconocida";
}
std::string undo() override {
if (!estado_anterior_.empty()) {
receiver_->setEstado(estado_anterior_);
return "Operación deshecha";
}
return "No se puede deshacer";
}
};
// Invocador que gestiona comandos
class Invoker {
private:
std::vector<std::shared_ptr<Command>> history_;
std::vector<std::shared_ptr<Command>> commands_to_execute_;
public:
void setCommand(std::shared_ptr<Command> command) {
commands_to_execute_.push_back(command);
}
std::vector<std::string> executeCommands() {
std::vector<std::string> results;
for (const auto& command : commands_to_execute_) {
std::string result = command->execute();
history_.push_back(command);
results.push_back(result);
}
commands_to_execute_.clear();
return results;
}
std::string undoLast() {
if (!history_.empty()) {
auto command = history_.back();
history_.pop_back();
return command->undo();
}
return "No hay comandos para deshacer";
}
};
// Uso
int main() {
auto receiver = std::make_shared<Receiver>();
auto invoker = std::make_unique<Invoker>();
// Configurar comandos
auto command1 = std::make_shared<ConcreteCommand>(receiver, "specialAction", "Hola Mundo");
auto command2 = std::make_shared<ConcreteCommand>(receiver, "anotherAction", 42);
invoker->setCommand(command1);
invoker->setCommand(command2);
std::cout << "Estado inicial: " << receiver->getEstado() << std::endl;
// Ejecutar comandos
auto results = invoker->executeCommands();
for (const auto& result : results) {
std::cout << "Resultado: " << result << std::endl;
}
std::cout << "Estado después de ejecutar: " << receiver->getEstado() << std::endl;
// Deshacer último comando
std::cout << invoker->undoLast() << std::endl;
std::cout << "Estado después de deshacer: " << receiver->getEstado() << std::endl;
// Deshacer otro comando
std::cout << invoker->undoLast() << std::endl;
std::cout << "Estado final: " << receiver->getEstado() << std::endl;
return 0;
}
Command para editor de texto
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <sstream>
// Editor de texto
class TextEditor {
private:
std::string texto_;
size_t cursor_posicion_;
public:
TextEditor() : texto_(""), cursor_posicion_(0) {}
std::string insertarTexto(const std::string& texto, size_t posicion = std::string::npos) {
if (posicion != std::string::npos) {
cursor_posicion_ = posicion;
}
// Insertar texto en la posición del cursor
texto_ = texto_.substr(0, cursor_posicion_) +
texto +
texto_.substr(cursor_posicion_);
cursor_posicion_ += texto.length();
return "Texto insertado: '" + texto + "'";
}
std::string borrarTexto(size_t cantidad = 1) {
if (cursor_posicion_ >= cantidad) {
size_t inicio = cursor_posicion_ - cantidad;
size_t fin = cursor_posicion_;
std::string texto_borrado = texto_.substr(inicio, cantidad);
texto_ = texto_.substr(0, inicio) + texto_.substr(fin);
cursor_posicion_ -= cantidad;
return "Texto borrado: '" + texto_borrado + "'";
}
return "No hay texto para borrar";
}
struct Estado {
std::string texto;
size_t cursor;
};
Estado obtenerEstado() const {
return {texto_, cursor_posicion_};
}
void establecerEstado(const Estado& estado) {
texto_ = estado.texto;
cursor_posicion_ = estado.cursor;
}
void mostrarEstado() const {
std::string contenido_mostrado = texto_;
if (cursor_posicion_ < contenido_mostrado.length()) {
contenido_mostrado = contenido_mostrado.substr(0, cursor_posicion_) + "|" +
contenido_mostrado.substr(cursor_posicion_);
} else {
contenido_mostrado += "|";
}
std::cout << "Texto: '" << contenido_mostrado << "'" << std::endl;
}
};
// Comando de inserción
class InsertCommand : public Command {
private:
std::shared_ptr<TextEditor> editor_;
std::string texto_;
size_t posicion_;
TextEditor::Estado estado_anterior_;
public:
InsertCommand(std::shared_ptr<TextEditor> editor, const std::string& texto, size_t posicion = std::string::npos)
: editor_(editor), texto_(texto), posicion_(posicion) {}
std::string execute() override {
estado_anterior_ = editor_->obtenerEstado();
return editor_->insertarTexto(texto_, posicion_);
}
std::string undo() override {
editor_->establecerEstado(estado_anterior_);
return "Deshecha inserción de '" + texto_ + "'";
}
};
// Comando de borrado
class DeleteCommand : public Command {
private:
std::shared_ptr<TextEditor> editor_;
size_t cantidad_;
TextEditor::Estado estado_anterior_;
std::string texto_borrado_;
public:
DeleteCommand(std::shared_ptr<TextEditor> editor, size_t cantidad = 1)
: editor_(editor), cantidad_(cantidad) {}
std::string execute() override {
estado_anterior_ = editor_->obtenerEstado();
std::string resultado = editor_->borrarTexto(cantidad_);
// Extraer texto borrado del resultado
size_t inicio = resultado.find("'");
size_t fin = resultado.find("'", inicio + 1);
if (inicio != std::string::npos && fin != std::string::npos) {
texto_borrado_ = resultado.substr(inicio + 1, fin - inicio - 1);
}
return resultado;
}
std::string undo() override {
editor_->establecerEstado(estado_anterior_);
return "Deshecho borrado de '" + texto_borrado_ + "'";
}
};
// Uso del editor
int main() {
auto editor = std::make_shared<TextEditor>();
auto invoker = std::make_unique<Invoker>();
std::cout << "=== Editor de Texto con Command Pattern ===" << std::endl;
// Comandos de inserción
invoker->setCommand(std::make_shared<InsertCommand>(editor, "Hola"));
invoker->setCommand(std::make_shared<InsertCommand>(editor, " Mundo"));
invoker->setCommand(std::make_shared<InsertCommand>(editor, "!", editor->obtenerEstado().texto.length()));
// Ejecutar todos los comandos
auto resultados = invoker->executeCommands();
for (const auto& resultado : resultados) {
std::cout << resultado << std::endl;
}
editor->mostrarEstado();
// Comando de borrado
invoker->setCommand(std::make_shared<DeleteCommand>(editor, 6)); // Borrar " Mundo"
auto resultado_borrado = invoker->executeCommands()[0];
std::cout << resultado_borrado << std::endl;
editor->mostrarEstado();
// Deshacer borrado
std::cout << invoker->undoLast() << std::endl;
editor->mostrarEstado();
return 0;
}
Paso 5: State - Cambio de comportamiento por estado
El patrón State permite que un objeto altere su comportamiento cuando su estado interno cambia. El objeto parecerá haber cambiado su clase.
Implementación básica
#include <iostream>
#include <memory>
// Interfaz abstracta para estados
class State {
public:
virtual ~State() = default;
virtual void handle(Context& context) = 0;
virtual std::string getName() const = 0;
};
// Contexto que mantiene el estado actual
class Context {
private:
std::unique_ptr<State> current_state_;
public:
explicit Context(std::unique_ptr<State> initial_state)
: current_state_(std::move(initial_state)) {}
void setState(std::unique_ptr<State> new_state) {
std::string old_name = current_state_->getName();
std::string new_name = new_state->getName();
std::cout << "Cambiando estado de " << old_name << " a " << new_name << std::endl;
current_state_ = std::move(new_state);
}
void request() {
current_state_->handle(*this);
}
const std::string& getCurrentStateName() const {
return current_state_->getName();
}
};
// Estados concretos
class ConcreteStateA : public State {
public:
void handle(Context& context) override {
std::cout << "Manejando en Estado A" << std::endl;
context.setState(std::make_unique<ConcreteStateB>());
}
std::string getName() const override {
return "Estado A";
}
};
class ConcreteStateB : public State {
public:
void handle(Context& context) override {
std::cout << "Manejando en Estado B" << std::endl;
context.setState(std::make_unique<ConcreteStateC>());
}
std::string getName() const override {
return "Estado B";
}
};
class ConcreteStateC : public State {
public:
void handle(Context& context) override {
std::cout << "Manejando en Estado C" << std::endl;
context.setState(std::make_unique<ConcreteStateA>());
}
std::string getName() const override {
return "Estado C";
}
};
// Uso
int main() {
auto context = std::make_unique<Context>(std::make_unique<ConcreteStateA>());
std::cout << "=== Patrón State ===" << std::endl;
for (int i = 0; i < 6; ++i) {
std::cout << "\nSolicitud " << (i + 1) << ":" << std::endl;
context->request();
std::cout << "Estado actual: " << context->getCurrentStateName() << std::endl;
}
return 0;
}
State para reproductor de música
#include <iostream>
#include <memory>
#include <string>
// Interfaz abstracta para estados del reproductor
class PlayerState {
public:
virtual ~PlayerState() = default;
virtual void play(MusicPlayer& player) = 0;
virtual void pause(MusicPlayer& player) = 0;
virtual void stop(MusicPlayer& player) = 0;
virtual std::string getName() const = 0;
};
// Reproductor de música
class MusicPlayer {
private:
std::unique_ptr<PlayerState> current_state_;
std::string current_song_;
public:
MusicPlayer() : current_song_("Canción de ejemplo") {
current_state_ = std::make_unique<StoppedState>();
}
void setState(std::unique_ptr<PlayerState> new_state) {
current_state_ = std::move(new_state);
}
void play() {
current_state_->play(*this);
}
void pause() {
current_state_->pause(*this);
}
void stop() {
current_state_->stop(*this);
}
void showStatus() const {
std::cout << "Estado actual: " << current_state_->getName()
<< " | Canción: " << current_song_ << std::endl;
}
const std::string& getCurrentSong() const {
return current_song_;
}
};
// Estado detenido
class StoppedState : public PlayerState {
public:
void play(MusicPlayer& player) override {
std::cout << "▶️ Iniciando reproducción" << std::endl;
player.setState(std::make_unique<PlayingState>());
}
void pause(MusicPlayer& player) override {
std::cout << "⏸️ No se puede pausar, el reproductor está detenido" << std::endl;
}
void stop(MusicPlayer& player) override {
std::cout << "⏹️ El reproductor ya está detenido" << std::endl;
}
std::string getName() const override {
return "Detenido";
}
};
// Estado reproduciendo
class PlayingState : public PlayerState {
public:
void play(MusicPlayer& player) override {
std::cout << "▶️ Ya se está reproduciendo" << std::endl;
}
void pause(MusicPlayer& player) override {
std::cout << "⏸️ Pausando reproducción" << std::endl;
player.setState(std::make_unique<PausedState>());
}
void stop(MusicPlayer& player) override {
std::cout << "⏹️ Deteniendo reproducción" << std::endl;
player.setState(std::make_unique<StoppedState>());
}
std::string getName() const override {
return "Reproduciendo";
}
};
// Estado pausado
class PausedState : public PlayerState {
public:
void play(MusicPlayer& player) override {
std::cout << "▶️ Reanudando reproducción" << std::endl;
player.setState(std::make_unique<PlayingState>());
}
void pause(MusicPlayer& player) override {
std::cout << "⏸️ Ya está pausado" << std::endl;
}
void stop(MusicPlayer& player) override {
std::cout << "⏹️ Deteniendo desde pausa" << std::endl;
player.setState(std::make_unique<StoppedState>());
}
std::string getName() const override {
return "Pausado";
}
};
// Uso
int main() {
MusicPlayer player;
std::cout << "=== Reproductor de Música con State Pattern ===" << std::endl;
std::vector<std::string> actions = {"play", "pause", "play", "pause", "stop", "pause"};
for (size_t i = 0; i < actions.size(); ++i) {
std::cout << "\n--- " << actions[i] << " ---" << std::endl;
if (actions[i] == "play") {
player.play();
} else if (actions[i] == "pause") {
player.pause();
} else if (actions[i] == "stop") {
player.stop();
}
player.showStatus();
}
return 0;
}
Paso 6: Template Method - Esqueleto de algoritmo
El patrón Template Method define el esqueleto de un algoritmo en una operación, delegando algunos pasos a las subclases. Permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura.
Implementación básica
#include <iostream>
#include <memory>
#include <vector>
// Clase abstracta que define el template method
class DataProcessor {
public:
virtual ~DataProcessor() = default;
// Template Method - define el esqueleto del algoritmo
void process() {
loadData();
transformData();
validateData();
saveData();
cleanup();
}
// Pasos abstractos que deben implementar las subclases
virtual void loadData() = 0;
virtual void transformData() = 0;
virtual void saveData() = 0;
// Pasos con implementación por defecto (hooks)
virtual void validateData() {
std::cout << "Validando datos (comportamiento por defecto)" << std::endl;
}
virtual void cleanup() {
std::cout << "Limpiando recursos (comportamiento por defecto)" << std::endl;
}
};
// Procesador para archivos CSV
class CSVProcessor : public DataProcessor {
public:
void loadData() override {
std::cout << "Cargando datos desde archivo CSV" << std::endl;
}
void transformData() override {
std::cout << "Transformando datos CSV: parseando, limpiando formato" << std::endl;
}
void saveData() override {
std::cout << "Guardando datos procesados en base de datos" << std::endl;
}
};
// Procesador para archivos JSON
class JSONProcessor : public DataProcessor {
public:
void loadData() override {
std::cout << "Cargando datos desde archivo JSON" << std::endl;
}
void transformData() override {
std::cout << "Transformando datos JSON: validando schema, convirtiendo tipos" << std::endl;
}
void validateData() override {
std::cout << "Validación especial para JSON: verificando estructura" << std::endl;
}
void saveData() override {
std::cout << "Guardando datos procesados en API REST" << std::endl;
}
};
// Procesador para archivos XML
class XMLProcessor : public DataProcessor {
public:
void loadData() override {
std::cout << "Cargando datos desde archivo XML" << std::endl;
}
void transformData() override {
std::cout << "Transformando datos XML: parseando, mapeando elementos" << std::endl;
}
void saveData() override {
std::cout << "Guardando datos procesados en sistema de archivos" << std::endl;
}
void cleanup() override {
std::cout << "Limpieza especial para XML: cerrando parsers" << std::endl;
}
};
// Uso
int main() {
std::cout << "=== Template Method Pattern ===" << std::endl;
std::cout << "\n=== Procesando CSV ===" << std::endl;
CSVProcessor csv_processor;
csv_processor.process();
std::cout << "\n=== Procesando JSON ===" << std::endl;
JSONProcessor json_processor;
json_processor.process();
std::cout << "\n=== Procesando XML ===" << std::endl;
XMLProcessor xml_processor;
xml_processor.process();
return 0;
}
Template Method para reportes
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
// Clase abstracta que define el template method para generar reportes
class ReportGenerator {
public:
virtual ~ReportGenerator() = default;
// Template Method - define el esqueleto del algoritmo
std::string generateReport() {
prepareData();
formatHeader();
formatBody();
formatFooter();
finalizeReport();
return getReport();
}
// Pasos comunes con implementación por defecto
virtual void prepareData() {
std::cout << "Preparando datos para el reporte" << std::endl;
}
virtual void formatFooter() {
std::cout << "Agregando pie de página estándar" << std::endl;
}
virtual void finalizeReport() {
std::cout << "Finalizando reporte" << std::endl;
}
// Pasos abstractos que deben implementar las subclases
virtual void formatHeader() = 0;
virtual void formatBody() = 0;
virtual std::string getReport() = 0;
};
// Generador de reportes HTML
class HTMLReport : public ReportGenerator {
private:
std::vector<std::string> content_;
public:
void formatHeader() override {
content_.push_back("<html><head><title>Reporte</title></head><body>");
content_.push_back("<h1>Reporte HTML</h1>");
}
void formatBody() override {
content_.push_back("<div class='contenido'>Contenido del reporte en HTML</div>");
}
void formatFooter() override {
content_.push_back("<footer>Reporte generado automáticamente</footer>");
content_.push_back("</body></html>");
}
std::string getReport() override {
std::ostringstream oss;
for (const auto& line : content_) {
oss << line << "\n";
}
return oss.str();
}
};
// Generador de reportes PDF
class PDFReport : public ReportGenerator {
private:
std::vector<std::string> content_;
public:
void formatHeader() override {
content_.push_back("=== REPORTE PDF ===");
content_.push_back("Título: Reporte en PDF");
}
void formatBody() override {
content_.push_back("Contenido del reporte en formato PDF");
}
std::string getReport() override {
std::ostringstream oss;
for (const auto& line : content_) {
oss << line << "\n";
}
return oss.str();
}
};
// Generador de reportes CSV
class CSVReport : public ReportGenerator {
private:
std::vector<std::string> content_;
public:
void formatHeader() override {
content_.push_back("columna1,columna2,columna3");
}
void formatBody() override {
content_.push_back("dato1,dato2,dato3");
content_.push_back("dato4,dato5,dato6");
}
// CSV no tiene pie de página tradicional
void formatFooter() override {
// No hacer nada para CSV
}
std::string getReport() override {
std::ostringstream oss;
for (const auto& line : content_) {
oss << line << "\n";
}
return oss.str();
}
};
// Uso
int main() {
std::cout << "=== Template Method para Reportes ===" << std::endl;
std::cout << "\n=== Generando Reporte HTML ===" << std::endl;
HTMLReport html_report;
std::string html = html_report.generateReport();
std::cout << html;
std::cout << "\n=== Generando Reporte PDF ===" << std::endl;
PDFReport pdf_report;
std::string pdf = pdf_report.generateReport();
std::cout << pdf;
std::cout << "\n=== Generando Reporte CSV ===" << std::endl;
CSVReport csv_report;
std::string csv = csv_report.generateReport();
std::cout << csv;
return 0;
}
Paso 7: Iterator - Recorrer colecciones
El patrón Iterator proporciona una forma de acceder secuencialmente a los elementos de un objeto agregado sin exponer su representación subyacente.
Implementación básica
#include <iostream>
#include <vector>
#include <memory>
#include <functional>
// Interfaz abstracta para iteradores
template <typename T>
class Iterator {
public:
virtual ~Iterator() = default;
virtual void first() = 0;
virtual void next() = 0;
virtual bool isDone() const = 0;
virtual T currentItem() const = 0;
};
// Interfaz abstracta para agregados
template <typename T>
class Aggregate {
public:
virtual ~Aggregate() = default;
virtual std::unique_ptr<Iterator<T>> createIterator() const = 0;
};
// Iterador concreto para listas
template <typename T>
class ListIterator : public Iterator<T> {
private:
const std::vector<T>& aggregate_;
size_t current_position_;
public:
explicit ListIterator(const std::vector<T>& aggregate)
: aggregate_(aggregate), current_position_(0) {}
void first() override {
current_position_ = 0;
}
void next() override {
if (current_position_ < aggregate_.size()) {
++current_position_;
}
}
bool isDone() const override {
return current_position_ >= aggregate_.size();
}
T currentItem() const override {
if (!isDone()) {
return aggregate_[current_position_];
}
throw std::out_of_range("Iterator is done");
}
};
// Agregado concreto - Lista
template <typename T>
class ConcreteList : public Aggregate<T> {
private:
std::vector<T> data_;
public:
void add(const T& item) {
data_.push_back(item);
}
const std::vector<T>& getData() const {
return data_;
}
std::unique_ptr<Iterator<T>> createIterator() const override {
return std::make_unique<ListIterator<T>>(data_);
}
};
// Iterador con filtro
template <typename T>
class FilterIterator : public Iterator<T> {
private:
std::vector<T> filtered_data_;
size_t current_position_;
public:
FilterIterator(const std::vector<T>& data, std::function<bool(const T&)> filter_func)
: current_position_(0) {
for (const auto& item : data) {
if (filter_func(item)) {
filtered_data_.push_back(item);
}
}
}
void first() override {
current_position_ = 0;
}
void next() override {
if (current_position_ < filtered_data_.size()) {
++current_position_;
}
}
bool isDone() const override {
return current_position_ >= filtered_data_.size();
}
T currentItem() const override {
if (!isDone()) {
return filtered_data_[current_position_];
}
throw std::out_of_range("Iterator is done");
}
};
// Uso
int main() {
std::cout << "=== Iterator Pattern ===" << std::endl;
// Crear lista y agregar elementos
ConcreteList<int> lista;
lista.add(1);
lista.add(2);
lista.add(3);
lista.add(4);
lista.add(5);
// Crear iterador y recorrer la lista
auto iterador = lista.createIterator();
std::cout << "Recorriendo lista:" << std::endl;
for (iterador->first(); !iterador->isDone(); iterador->next()) {
std::cout << "Elemento actual: " << iterador->currentItem() << std::endl;
}
// Iterador con filtro para números pares
std::cout << "\nNúmeros pares:" << std::endl;
FilterIterator<int> filter_iterador(lista.getData(),
[](const int& num) { return num % 2 == 0; });
for (filter_iterador.first(); !filter_iterador.isDone(); filter_iterador.next()) {
std::cout << "Par: " << filter_iterador.currentItem() << std::endl;
}
return 0;
}
Iterator para árbol binario
#include <iostream>
#include <vector>
#include <memory>
#include <stack>
// Nodo del árbol binario
struct TreeNode {
int valor;
std::unique_ptr<TreeNode> izquierda;
std::unique_ptr<TreeNode> derecha;
TreeNode(int val) : valor(val), izquierda(nullptr), derecha(nullptr) {}
};
// Árbol binario
class BinaryTree {
private:
std::unique_ptr<TreeNode> raiz_;
void insertRecursive(std::unique_ptr<TreeNode>& nodo, int valor) {
if (!nodo) {
nodo = std::make_unique<TreeNode>(valor);
} else if (valor < nodo->valor) {
insertRecursive(nodo->izquierda, valor);
} else {
insertRecursive(nodo->derecha, valor);
}
}
public:
BinaryTree() : raiz_(nullptr) {}
void insertar(int valor) {
insertRecursive(raiz_, valor);
}
const std::unique_ptr<TreeNode>& getRaiz() const {
return raiz_;
}
};
// Interfaz abstracta para iteradores de árbol
class TreeIterator {
public:
virtual ~TreeIterator() = default;
virtual void first() = 0;
virtual void next() = 0;
virtual bool isDone() const = 0;
virtual int currentItem() const = 0;
};
// Iterador Inorden (izquierda, raíz, derecha)
class InorderIterator : public TreeIterator {
private:
const TreeNode* raiz_;
std::stack<const TreeNode*> pila_;
void goLeft(const TreeNode* nodo) {
while (nodo) {
pila_.push(nodo);
nodo = nodo->izquierda.get();
}
}
public:
explicit InorderIterator(const TreeNode* raiz) : raiz_(raiz) {
first();
}
void first() override {
// Limpiar pila
while (!pila_.empty()) {
pila_.pop();
}
goLeft(raiz_);
}
void next() override {
if (!pila_.empty()) {
const TreeNode* nodo = pila_.top();
pila_.pop();
// Si hay subárbol derecho, recorrerlo
if (nodo->derecha) {
goLeft(nodo->derecha.get());
}
}
}
bool isDone() const override {
return pila_.empty();
}
int currentItem() const override {
if (!isDone()) {
return pila_.top()->valor;
}
throw std::out_of_range("Iterator is done");
}
};
// Iterador Preorden (raíz, izquierda, derecha)
class PreorderIterator : public TreeIterator {
private:
const TreeNode* raiz_;
std::stack<const TreeNode*> pila_;
public:
explicit PreorderIterator(const TreeNode* raiz) : raiz_(raiz) {
first();
}
void first() override {
// Limpiar pila
while (!pila_.empty()) {
pila_.pop();
}
if (raiz_) {
pila_.push(raiz_);
}
}
void next() override {
if (!pila_.empty()) {
const TreeNode* nodo = pila_.top();
pila_.pop();
// Agregar primero derecha, luego izquierda (para que izquierda se procese primero)
if (nodo->derecha) {
pila_.push(nodo->derecha.get());
}
if (nodo->izquierda) {
pila_.push(nodo->izquierda.get());
}
}
}
bool isDone() const override {
return pila_.empty();
}
int currentItem() const override {
if (!isDone()) {
return pila_.top()->valor;
}
throw std::out_of_range("Iterator is done");
}
};
// Iterador Postorden (izquierda, derecha, raíz)
class PostorderIterator : public TreeIterator {
private:
const TreeNode* raiz_;
std::stack<const TreeNode*> pila_;
void preparePostorder(const TreeNode* nodo) {
if (nodo) {
pila_.push(nodo);
if (nodo->derecha) {
preparePostorder(nodo->derecha.get());
}
if (nodo->izquierda) {
preparePostorder(nodo->izquierda.get());
}
}
}
public:
explicit PostorderIterator(const TreeNode* raiz) : raiz_(raiz) {
first();
}
void first() override {
// Limpiar pila
while (!pila_.empty()) {
pila_.pop();
}
preparePostorder(raiz_);
}
void next() override {
if (!pila_.empty()) {
pila_.pop();
}
}
bool isDone() const override {
return pila_.empty();
}
int currentItem() const override {
if (!isDone()) {
return pila_.top()->valor;
}
throw std::out_of_range("Iterator is done");
}
};
// Uso del árbol con diferentes iteradores
int main() {
std::cout << "=== Iterator Pattern para Árbol Binario ===" << std::endl;
BinaryTree arbol;
std::vector<int> valores = {5, 3, 7, 2, 4, 6, 8};
for (int valor : valores) {
arbol.insertar(valor);
}
std::cout << "=== Recorrido Inorden (ordenado) ===" << std::endl;
InorderIterator inorden(arbol.getRaiz().get());
for (inorden.first(); !inorden.isDone(); inorden.next()) {
std::cout << "Valor: " << inorden.currentItem() << std::endl;
}
std::cout << "\n=== Recorrido Preorden ===" << std::endl;
PreorderIterator preorden(arbol.getRaiz().get());
for (preorden.first(); !preorden.isDone(); preorden.next()) {
std::cout << "Valor: " << preorden.currentItem() << std::endl;
}
std::cout << "\n=== Recorrido Postorden ===" << std::endl;
PostorderIterator postorden(arbol.getRaiz().get());
for (postorden.first(); !postorden.isDone(); postorden.next()) {
std::cout << "Valor: " << postorden.currentItem() << std::endl;
}
return 0;
}
Paso 8: Mediator - Comunicación centralizada
El patrón Mediator define un objeto que encapsula cómo un conjunto de objetos interactúa. Promueve el acoplamiento débil al evitar que los objetos se refieran entre sí explícitamente, lo cual es especialmente útil en C++ para mantener la encapsulación y facilitar el testing.
Implementación básica
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <algorithm>
// Interfaz abstracta para colegas
class Colega;
class Mediador {
public:
virtual ~Mediador() = default;
virtual void registrarColega(std::shared_ptr<Colega> colega) = 0;
virtual void enviarMensaje(const std::string& mensaje,
std::shared_ptr<Colega> remitente,
const std::string& destinatario = "") = 0;
};
class Colega : public std::enable_shared_from_this<Colega> {
protected:
std::string nombre_;
std::shared_ptr<Mediador> mediador_;
public:
Colega(const std::string& nombre, std::shared_ptr<Mediador> mediador)
: nombre_(nombre), mediador_(mediador) {}
virtual ~Colega() = default;
const std::string& getNombre() const { return nombre_; }
virtual void enviar(const std::string& mensaje, const std::string& destinatario = "") = 0;
virtual void recibir(const std::string& mensaje, const std::string& remitente) = 0;
};
// Mediador concreto
class MediadorConcreto : public Mediador {
private:
std::unordered_map<std::string, std::shared_ptr<Colega>> colegas_;
public:
void registrarColega(std::shared_ptr<Colega> colega) override {
colegas_[colega->getNombre()] = colega;
}
void enviarMensaje(const std::string& mensaje,
std::shared_ptr<Colega> remitente,
const std::string& destinatario = "") override {
if (!destinatario.empty()) {
// Mensaje privado
auto it = colegas_.find(destinatario);
if (it != colegas_.end()) {
it->second->recibir(mensaje, remitente->getNombre());
} else {
std::cout << "❌ Destinatario '" << destinatario << "' no encontrado" << std::endl;
}
} else {
// Broadcast a todos excepto remitente
for (const auto& par : colegas_) {
if (par.first != remitente->getNombre()) {
par.second->recibir(mensaje, remitente->getNombre());
}
}
}
}
};
// Colega concreto
class ColegaConcreto : public Colega {
public:
ColegaConcreto(const std::string& nombre, std::shared_ptr<Mediador> mediador)
: Colega(nombre, mediador) {}
void enviar(const std::string& mensaje, const std::string& destinatario = "") override {
std::cout << "📤 " << nombre_ << " enviando mensaje: '" << mensaje << "'" << std::endl;
mediador_->enviarMensaje(mensaje, shared_from_this(), destinatario);
}
void recibir(const std::string& mensaje, const std::string& remitente) override {
std::cout << "📥 " << nombre_ << " recibió de " << remitente << ": '" << mensaje << "'" << std::endl;
}
};
// Uso del patrón Mediator
int main() {
std::cout << "=== Mediator Pattern - Comunicación Centralizada ===" << std::endl;
auto mediador = std::make_shared<MediadorConcreto>();
// Crear colegas
auto colega1 = std::make_shared<ColegaConcreto>("Usuario1", mediador);
auto colega2 = std::make_shared<ColegaConcreto>("Usuario2", mediador);
auto colega3 = std::make_shared<ColegaConcreto>("Usuario3", mediador);
auto colega4 = std::make_shared<ColegaConcreto>("Usuario4", mediador);
// Registrar colegas
mediador->registrarColega(colega1);
mediador->registrarColega(colega2);
mediador->registrarColega(colega3);
mediador->registrarColega(colega4);
std::cout << "\n=== Comunicación con Mediador ===" << std::endl;
// Mensajes broadcast
colega1->enviar("¡Hola a todos!");
colega2->enviar("¡Hola! ¿Cómo están?");
// Mensajes privados
colega3->enviar("Mensaje secreto para Usuario1", "Usuario1");
colega1->enviar("Respuesta privada", "Usuario3");
// Usuario no existente
colega2->enviar("Mensaje para usuario inexistente", "Usuario99");
return 0;
}
Mediator para sistema de chat
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
// Usuario de chat
class UsuarioChat;
class MediadorChat;
class UsuarioChat : public Colega {
private:
std::string sala_actual_;
public:
UsuarioChat(const std::string& nombre, std::shared_ptr<MediadorChat> mediador, const std::string& sala_inicial);
void enviar(const std::string& mensaje, const std::string& destinatario = "") override;
void recibir(const std::string& mensaje, const std::string& remitente) override;
void unirseSala(const std::string& sala);
void salirSala();
const std::string& getSalaActual() const { return sala_actual_; }
};
class MediadorChat : public MediadorConcreto {
private:
std::unordered_map<std::string, std::vector<std::shared_ptr<UsuarioChat>>> salas_;
public:
void registrarColega(std::shared_ptr<Colega> colega) override;
void enviarMensaje(const std::string& mensaje,
std::shared_ptr<Colega> remitente,
const std::string& destinatario = "") override;
void actualizarSala(std::shared_ptr<UsuarioChat> usuario, const std::string& nueva_sala);
private:
void agregarASala(std::shared_ptr<UsuarioChat> usuario, const std::string& sala);
void removerDeSala(std::shared_ptr<UsuarioChat> usuario, const std::string& sala);
};
// Implementación de UsuarioChat
UsuarioChat::UsuarioChat(const std::string& nombre, std::shared_ptr<MediadorChat> mediador, const std::string& sala_inicial)
: Colega(nombre, mediador), sala_actual_(sala_inicial) {}
void UsuarioChat::enviar(const std::string& mensaje, const std::string& destinatario) {
std::cout << "💬 " << nombre_ << " en " << sala_actual_ << ": '" << mensaje << "'" << std::endl;
auto mediador_chat = std::static_pointer_cast<MediadorChat>(mediador_);
mediador_chat->enviarMensaje(mensaje, shared_from_this(), destinatario);
}
void UsuarioChat::recibir(const std::string& mensaje, const std::string& remitente) {
std::cout << "💭 " << nombre_ << " recibió de " << remitente << ": '" << mensaje << "'" << std::endl;
}
void UsuarioChat::unirseSala(const std::string& sala) {
std::cout << "🚪 " << nombre_ << " se unió a " << sala << std::endl;
auto mediador_chat = std::static_pointer_cast<MediadorChat>(mediador_);
mediador_chat->actualizarSala(std::static_pointer_cast<UsuarioChat>(shared_from_this()), sala);
sala_actual_ = sala;
}
void UsuarioChat::salirSala() {
std::cout << "🚪 " << nombre_ << " salió de " << sala_actual_ << std::endl;
auto mediador_chat = std::static_pointer_cast<MediadorChat>(mediador_);
mediador_chat->actualizarSala(std::static_pointer_cast<UsuarioChat>(shared_from_this()), "");
sala_actual_ = "";
}
// Implementación de MediadorChat
void MediadorChat::registrarColega(std::shared_ptr<Colega> colega) {
MediadorConcreto::registrarColega(colega);
auto usuario = std::static_pointer_cast<UsuarioChat>(colega);
agregarASala(usuario, usuario->getSalaActual());
}
void MediadorChat::enviarMensaje(const std::string& mensaje,
std::shared_ptr<Colega> remitente,
const std::string& destinatario) {
if (!destinatario.empty()) {
// Mensaje privado
MediadorConcreto::enviarMensaje(mensaje, remitente, destinatario);
} else {
// Mensaje a toda la sala
auto usuario = std::static_pointer_cast<UsuarioChat>(remitente);
auto sala = usuario->getSalaActual();
auto it = salas_.find(sala);
if (it != salas_.end()) {
for (auto& colega : it->second) {
if (colega->getNombre() != usuario->getNombre()) {
colega->recibir(mensaje, usuario->getNombre());
}
}
}
}
}
void MediadorChat::actualizarSala(std::shared_ptr<UsuarioChat> usuario, const std::string& nueva_sala) {
// Remover de sala actual
if (!usuario->getSalaActual().empty()) {
removerDeSala(usuario, usuario->getSalaActual());
}
// Agregar a nueva sala
if (!nueva_sala.empty()) {
agregarASala(usuario, nueva_sala);
}
}
void MediadorChat::agregarASala(std::shared_ptr<UsuarioChat> usuario, const std::string& sala) {
salas_[sala].push_back(usuario);
}
void MediadorChat::removerDeSala(std::shared_ptr<UsuarioChat> usuario, const std::string& sala) {
auto it = salas_.find(sala);
if (it != salas_.end()) {
auto& usuarios = it->second;
usuarios.erase(std::remove_if(usuarios.begin(), usuarios.end(),
[&usuario](const std::shared_ptr<UsuarioChat>& u) {
return u->getNombre() == usuario->getNombre();
}), usuarios.end());
}
}
// Uso del sistema de chat
int main() {
std::cout << "=== Sistema de Chat con Salas ===" << std::endl;
auto mediador_chat = std::make_shared<MediadorChat>();
// Crear usuarios
auto usuario1 = std::make_shared<UsuarioChat>("Ana", mediador_chat, "general");
auto usuario2 = std::make_shared<UsuarioChat>("Carlos", mediador_chat, "general");
auto usuario3 = std::make_shared<UsuarioChat>("Maria", mediador_chat, "tecnologia");
auto usuario4 = std::make_shared<UsuarioChat>("Pedro", mediador_chat, "tecnologia");
// Registrar usuarios
mediador_chat->registrarColega(usuario1);
mediador_chat->registrarColega(usuario2);
mediador_chat->registrarColega(usuario3);
mediador_chat->registrarColega(usuario4);
// Mensajes en salas
usuario1->enviar("¡Hola a todos en general!");
usuario3->enviar("Discutiendo sobre Python en tecnología");
// Cambio de sala
usuario2->unirseSala("tecnologia");
usuario2->enviar("¡Hola! Me uní a tecnología");
// Mensaje privado
usuario1->enviar("Mensaje privado para Maria", "Maria");
// Mostrar estado de salas
std::cout << "\n=== Estado de Salas ===" << std::endl;
for (const auto& par : mediador_chat->salas_) {
std::cout << "Sala '" << par.first << "': ";
for (size_t i = 0; i < par.second.size(); ++i) {
if (i > 0) std::cout << ", ";
std::cout << par.second[i]->getNombre();
}
std::cout << std::endl;
}
return 0;
}
Paso 9: Memento - Guardar y restaurar estado
El patrón Memento captura y externaliza el estado interno de un objeto sin violar la encapsulación, para que el objeto pueda ser restaurado a este estado más tarde. En C++, este patrón es especialmente útil para implementar funcionalidades de "undo" y "redo" de manera segura y eficiente.
Implementación básica
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <stack>
// Memento: almacena el estado del originador
class Memento {
private:
std::string estado_;
friend class Originador; // Permite acceso directo al estado
public:
explicit Memento(const std::string& estado) : estado_(estado) {}
const std::string& getEstado() const {
return estado_;
}
private:
// Constructor privado para que solo Originador pueda crear mementos
Memento() = default;
};
// Originador: crea y usa mementos para guardar/restaurar su estado
class Originador {
private:
std::string estado_;
public:
void establecerEstado(const std::string& estado) {
std::cout << "Estableciendo estado: " << estado << std::endl;
estado_ = estado;
}
const std::string& obtenerEstado() const {
return estado_;
}
// Crear memento con el estado actual
std::unique_ptr<Memento> guardarEnMemento() const {
std::cout << "Guardando estado en memento: " << estado_ << std::endl;
return std::make_unique<Memento>(estado_);
}
// Restaurar estado desde memento
void restaurarDesdeMemento(const Memento& memento) {
estado_ = memento.estado_;
std::cout << "Estado restaurado desde memento: " << estado_ << std::endl;
}
};
// Cuidador: gestiona los mementos (historial de estados)
class Cuidador {
private:
Originador& originador_;
std::stack<std::unique_ptr<Memento>> historial_;
public:
explicit Cuidador(Originador& originador) : originador_(originador) {}
// Guardar estado actual en el historial
void guardar() {
auto memento = originador_.guardarEnMemento();
historial_.push(std::move(memento));
std::cout << "Memento guardado. Total: " << historial_.size() << std::endl;
}
// Deshacer último cambio
bool deshacer() {
if (!historial_.empty()) {
auto memento = std::move(historial_.top());
historial_.pop();
originador_.restaurarDesdeMemento(*memento);
return true;
}
return false;
}
// Obtener número de estados guardados
size_t getTamanoHistorial() const {
return historial_.size();
}
};
// Uso del patrón Memento
int main() {
std::cout << "=== Memento Pattern - Guardar y Restaurar Estado ===" << std::endl;
Originador originador;
Cuidador cuidador(originador);
// Crear y guardar estados
originador.establecerEstado("Estado 1");
cuidador.guardar();
originador.establecerEstado("Estado 2");
cuidador.guardar();
originador.establecerEstado("Estado 3");
cuidador.guardar();
std::cout << "Estado actual: " << originador.obtenerEstado() << std::endl;
// Deshacer cambios
cuidador.deshacer();
cuidador.deshacer();
std::cout << "Estado después de deshacer 2 veces: " << originador.obtenerEstado() << std::endl;
return 0;
}
Memento para editor de texto
#include <iostream>
#include <string>
#include <memory>
#include <stack>
#include <utility>
// Estado del documento
struct DocumentState {
std::string contenido;
size_t cursor_posicion;
DocumentState(const std::string& cont, size_t cursor)
: contenido(cont), cursor_posicion(cursor) {}
};
// Editor de texto
class TextEditor {
private:
std::string contenido_;
size_t cursor_posicion_;
public:
TextEditor() : contenido_(""), cursor_posicion_(0) {}
void escribirTexto(const std::string& texto) {
// Insertar texto en la posición del cursor
contenido_ = contenido_.substr(0, cursor_posicion_) +
texto +
contenido_.substr(cursor_posicion_);
cursor_posicion_ += texto.length();
std::cout << "📝 Escrito: '" << texto << "'" << std::endl;
}
void borrarCaracter(size_t cantidad = 1) {
if (cursor_posicion_ >= cantidad) {
size_t inicio = cursor_posicion_ - cantidad;
size_t fin = cursor_posicion_;
std::string texto_borrado = contenido_.substr(inicio, cantidad);
contenido_ = contenido_.substr(0, inicio) + contenido_.substr(fin);
cursor_posicion_ -= cantidad;
std::cout << "⌫ Borrado: '" << texto_borrado << "'" << std::endl;
} else {
std::cout << "No hay texto para borrar" << std::endl;
}
}
void moverCursor(size_t posicion) {
if (posicion <= contenido_.length()) {
cursor_posicion_ = posicion;
std::cout << "📍 Cursor movido a posición " << posicion << std::endl;
}
}
DocumentState obtenerEstado() const {
return DocumentState(contenido_, cursor_posicion_);
}
void establecerEstado(const DocumentState& estado) {
contenido_ = estado.contenido;
cursor_posicion_ = estado.cursor_posicion;
}
void mostrarEstado() const {
std::string contenido_mostrado = contenido_;
if (cursor_posicion_ < contenido_mostrado.length()) {
contenido_mostrado = contenido_mostrado.substr(0, cursor_posicion_) + "|" +
contenido_mostrado.substr(cursor_posicion_);
} else {
contenido_mostrado += "|";
}
std::cout << "📄 Contenido: '" << contenido_mostrado << "'" << std::endl;
}
const std::string& getContenido() const { return contenido_; }
size_t getCursorPosicion() const { return cursor_posicion_; }
};
// Historial del editor usando Memento
class EditorHistory {
private:
TextEditor& editor_;
std::stack<DocumentState> historial_;
public:
explicit EditorHistory(TextEditor& editor) : editor_(editor) {}
void guardarEstado() {
historial_.push(editor_.obtenerEstado());
std::cout << "💾 Estado guardado (total: " << historial_.size() << ")" << std::endl;
}
bool deshacer() {
if (!historial_.empty()) {
DocumentState estado = historial_.top();
historial_.pop();
editor_.establecerEstado(estado);
std::cout << "🔄 Deshecho último cambio" << std::endl;
return true;
}
std::cout << "❌ No hay cambios para deshacer" << std::endl;
return false;
}
size_t getTamanoHistorial() const {
return historial_.size();
}
};
// Uso del editor con Memento
int main() {
std::cout << "=== Memento para Editor de Texto ===" << std::endl;
TextEditor editor;
EditorHistory historial(editor);
editor.mostrarEstado();
editor.escribirTexto("Hola");
historial.guardarEstado();
editor.mostrarEstado();
editor.escribirTexto(" mundo");
historial.guardarEstado();
editor.mostrarEstado();
editor.borrarCaracter(6); // Borrar " mundo"
editor.mostrarEstado();
std::cout << "\n=== Deshaciendo ===" << std::endl;
historial.deshacer();
editor.mostrarEstado();
historial.deshacer();
editor.mostrarEstado();
return 0;
}
Paso 10: Comparación y selección de patrones
Cuándo usar cada patrón de comportamiento en C++
| Patrón | Cuándo usarlo | Ventajas en C++ | Desventajas en C++ |
|---|---|---|---|
| Observer | Notificaciones, eventos, sistemas reactivos | Type safety, RAII, thread-safe con mutex | Overhead de virtual functions, gestión de lifetime |
| Strategy | Algoritmos variables, políticas configurables | Templates para type safety, inlining, zero-cost abstractions | Code bloat con templates, complejidad de compilación |
| Command | Operaciones reversibles, colas, transacciones | RAII para cleanup, move semantics, smart pointers | Overhead de indirección, memoria extra para commands |
| State | Estados complejos, máquinas de estado | Performance óptima, inlining de estados, type safety | Code duplication, difícil de extender |
| Template Method | Algoritmos con pasos variables, frameworks | Non-virtual interface idiom, CRTP, compile-time polymorphism | Tight coupling, difícil de mockear para testing |
| Iterator | Recorrer colecciones, lazy evaluation | STL integration, range-based for loops, memory safety | Overhead de indirección, complejidad de implementación |
| Mediator | Comunicación compleja, GUI, sistemas distribuidos | Encapsulation fuerte, thread safety, RAII | Single responsibility violation, testing complexity |
| Memento | Undo/redo, snapshots, serialization | Move semantics, RAII, memory safety | Memory overhead, serialization complexity |
Consideraciones específicas de C++ para cada patrón
Observer en C++
// ✅ Recomendado: Usar std::shared_ptr para gestión automática de memoria
class Subject {
std::vector<std::shared_ptr<Observer>> observers_;
public:
void attach(std::shared_ptr<Observer> observer) {
observers_.push_back(observer);
}
};
// ❌ Evitar: Raw pointers pueden causar memory leaks
class BadSubject {
std::vector<Observer*> observers_; // ¡Peligroso!
};
Strategy en C++
// ✅ Recomendado: Usar templates para type safety y performance
template <typename T>
class SortStrategy {
virtual std::vector<T> sort(const std::vector<T>&) = 0;
};
// ✅ Alternativa: Usar std::function para máxima flexibilidad
using SortFunction = std::function<void(std::vector<int>&)>;
class FlexibleSortStrategy {
SortFunction sort_func_;
public:
void setSortFunction(SortFunction func) { sort_func_ = func; }
};
Command en C++
// ✅ Recomendado: Usar move semantics y RAII
class Command {
std::unique_ptr<Receiver> receiver_; // RAII
public:
Command(std::unique_ptr<Receiver> r) : receiver_(std::move(r)) {}
void execute() { /* usar receiver_ */ }
};
// ✅ Usar lambdas para comandos simples
auto simple_command = std::make_shared<LambdaCommand>(
[]() { std::cout << "Ejecutando lambda" << std::endl; },
[]() { std::cout << "Deshaciendo lambda" << std::endl; }
);
State en C++
// ✅ Recomendado: State pattern con stack allocation
class Context {
std::unique_ptr<State> current_state_;
public:
void changeState(std::unique_ptr<State> new_state) {
current_state_ = std::move(new_state);
}
};
// ✅ Alternativa: Usar enum class para estados simples
enum class PlayerState { Stopped, Playing, Paused };
class SimplePlayer {
PlayerState state_ = PlayerState::Stopped;
public:
void play() {
if (state_ == PlayerState::Stopped || state_ == PlayerState::Paused) {
state_ = PlayerState::Playing;
}
}
};
Template Method en C++
// ✅ Recomendado: Non-Virtual Interface (NVI) idiom
class Algorithm {
public:
void process() { // Template method - no virtual
setup();
doProcess(); // Virtual hook
cleanup();
}
private:
virtual void doProcess() = 0;
void setup() { /* default implementation */ }
void cleanup() { /* default implementation */ }
};
// ✅ Usar CRTP para static polymorphism
template <typename Derived>
class CRTPAlgorithm {
public:
void process() {
static_cast<Derived*>(this)->doProcess();
}
};
Iterator en C++
// ✅ Recomendado: Usar STL iterator concepts
class MyContainer {
public:
class iterator {
using iterator_category = std::forward_iterator_tag;
using value_type = MyType;
// ... otros typedefs requeridos
};
iterator begin() { return iterator(data_.begin()); }
iterator end() { return iterator(data_.end()); }
private:
std::vector<MyType> data_;
};
// ✅ Usar range-based for loops
for (const auto& item : my_container) {
// procesar item
}
Mediator en C++
// ✅ Recomendado: Usar weak_ptr para evitar circular references
class Mediator {
std::unordered_map<std::string, std::weak_ptr<Colleague>> colleagues_;
public:
void registerColleague(const std::string& name, std::shared_ptr<Colleague> c) {
colleagues_[name] = c;
}
};
// ✅ Usar para GUI frameworks
class DialogMediator {
std::shared_ptr<Button> ok_button_;
std::shared_ptr<TextField> input_field_;
public:
void buttonClicked() {
if (input_field_->isValid()) {
ok_button_->enable();
}
}
};
Memento en C++
// ✅ Recomendado: Usar smart pointers y move semantics
class Memento {
std::unique_ptr<InternalState> state_;
public:
Memento(std::unique_ptr<InternalState> s) : state_(std::move(s)) {}
const InternalState& getState() const { return *state_; }
};
// ✅ Usar para undo/redo en editores
class TextEditor {
std::stack<std::unique_ptr<DocumentMemento>> undo_stack_;
std::stack<std::unique_ptr<DocumentMemento>> redo_stack_;
public:
void saveState() {
undo_stack_.push(createMemento());
// Clear redo stack when new action is performed
while (!redo_stack_.empty()) redo_stack_.pop();
}
};
Paso 11: Proyecto práctico - Editor de texto
Vamos a crear un editor de texto completo que combine múltiples patrones de comportamiento en C++.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <stack>
#include <functional>
#include <unordered_map>
#include <chrono>
#include <thread>
// Memento para estados del documento
struct DocumentState {
std::string contenido;
size_t cursor_posicion;
std::pair<size_t, size_t> seleccion;
DocumentState(const std::string& cont, size_t cursor, std::pair<size_t, size_t> sel = {0, 0})
: contenido(cont), cursor_posicion(cursor), seleccion(sel) {}
};
// Command para operaciones del editor
class EditorCommand {
public:
virtual ~EditorCommand() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
// Observer para notificaciones de cambios
class EditorObserver {
public:
virtual ~EditorObserver() = default;
virtual void update(const std::string& event_type) = 0;
};
// State para modos del editor
class EditorState {
public:
virtual ~EditorState() = default;
virtual void handleKey(class TextEditor& editor, char key) = 0;
virtual std::string getName() const = 0;
};
// Strategy para diferentes estrategias de guardado
class SaveStrategy {
public:
virtual ~SaveStrategy() = default;
virtual void save(const std::string& contenido, const std::string& nombre_archivo) = 0;
};
// Editor principal que combina múltiples patrones
class TextEditor {
private:
std::string contenido_;
size_t cursor_posicion_;
std::unique_ptr<EditorState> estado_actual_;
std::vector<std::shared_ptr<EditorObserver>> observadores_;
std::vector<std::shared_ptr<EditorCommand>> historial_comandos_;
int indice_historial_;
std::unordered_map<std::string, std::unique_ptr<SaveStrategy>> estrategias_guardado_;
public:
TextEditor() : contenido_(""), cursor_posicion_(0), indice_historial_(-1) {
cambiarEstado(std::make_unique<NormalState>());
}
// Observer Pattern
void agregarObservador(std::shared_ptr<EditorObserver> observador) {
observadores_.push_back(observador);
}
void notificarObservadores(const std::string& evento) {
for (const auto& observador : observadores_) {
observador->update(evento);
}
}
// State Pattern
void cambiarEstado(std::unique_ptr<EditorState> nuevo_estado) {
estado_actual_ = std::move(nuevo_estado);
std::cout << "🔄 Modo cambiado a: " << estado_actual_->getName() << std::endl;
}
void procesarTecla(char tecla) {
estado_actual_->handleKey(*this, tecla);
}
// Command Pattern
void ejecutarComando(std::shared_ptr<EditorCommand> comando) {
comando->execute();
// Agregar al historial (remover comandos "futuros" si estamos en medio del historial)
if (indice_historial_ + 1 < static_cast<int>(historial_comandos_.size())) {
historial_comandos_.resize(indice_historial_ + 1);
}
historial_comandos_.push_back(comando);
indice_historial_++;
notificarObservadores("contenido_cambiado");
}
void deshacer() {
if (indice_historial_ >= 0) {
historial_comandos_[indice_historial_]->undo();
indice_historial_--;
notificarObservadores("contenido_cambiado");
std::cout << "🔄 Comando deshecho" << std::endl;
}
}
// Strategy Pattern
void registrarEstrategiaGuardado(const std::string& nombre, std::unique_ptr<SaveStrategy> estrategia) {
estrategias_guardado_[nombre] = std::move(estrategia);
}
void guardar(const std::string& nombre_archivo, const std::string& estrategia = "local") {
auto it = estrategias_guardado_.find(estrategia);
if (it != estrategias_guardado_.end()) {
it->second->save(contenido_, nombre_archivo);
} else {
std::cout << "❌ Estrategia de guardado no encontrada: " << estrategia << std::endl;
}
}
// Métodos de edición
void escribirTexto(const std::string& texto) {
contenido_ = contenido_.substr(0, cursor_posicion_) +
texto +
contenido_.substr(cursor_posicion_);
cursor_posicion_ += texto.length();
std::cout << "📝 Escrito: '" << texto << "'" << std::endl;
}
void borrarCaracter(size_t cantidad = 1) {
if (cursor_posicion_ >= cantidad) {
size_t inicio = cursor_posicion_ - cantidad;
size_t fin = cursor_posicion_;
std::string texto_borrado = contenido_.substr(inicio, cantidad);
contenido_ = contenido_.substr(0, inicio) + contenido_.substr(fin);
cursor_posicion_ -= cantidad;
std::cout << "⌫ Borrado: '" << texto_borrado << "'" << std::endl;
} else {
std::cout << "No hay texto para borrar" << std::endl;
}
}
void moverCursor(size_t posicion) {
if (posicion <= contenido_.length()) {
cursor_posicion_ = posicion;
notificarObservadores("cursor_movido");
std::cout << "📍 Cursor movido a posición " << posicion << std::endl;
}
}
// Getters
const std::string& getContenido() const { return contenido_; }
size_t getCursorPosicion() const { return cursor_posicion_; }
const std::string& getEstadoActual() const { return estado_actual_->getName(); }
void mostrarEstado() const {
std::string contenido_mostrado = contenido_;
if (cursor_posicion_ < contenido_mostrado.length()) {
contenido_mostrado = contenido_mostrado.substr(0, cursor_posicion_) + "|" +
contenido_mostrado.substr(cursor_posicion_);
} else {
contenido_mostrado += "|";
}
std::cout << "📄 [" << estado_actual_->getName() << "] '" << contenido_mostrado << "'" << std::endl;
}
};
// Estados concretos
class NormalState : public EditorState {
public:
void handleKey(TextEditor& editor, char key) override {
if (key == 'i') {
editor.cambiarEstado(std::make_unique<InsertState>());
} else if (key == 'x') {
editor.borrarCaracter();
} else if (key == 'u') {
editor.deshacer();
}
}
std::string getName() const override {
return "NORMAL";
}
};
class InsertState : public EditorState {
public:
void handleKey(TextEditor& editor, char key) override {
if (key == 27) { // Escape key
editor.cambiarEstado(std::make_unique<NormalState>());
} else if (key == 127 || key == 8) { // Backspace
editor.borrarCaracter();
} else if (key >= 32 && key <= 126) { // Caracter imprimible
editor.escribirTexto(std::string(1, key));
}
}
std::string getName() const override {
return "INSERTAR";
}
};
// Comandos concretos
class InsertCommand : public EditorCommand {
private:
std::shared_ptr<TextEditor> editor_;
std::string texto_;
size_t posicion_;
std::string texto_anterior_;
public:
InsertCommand(std::shared_ptr<TextEditor> editor, const std::string& texto, size_t posicion = std::string::npos)
: editor_(editor), texto_(texto), posicion_(posicion) {}
void execute() override {
if (posicion_ != std::string::npos) {
editor_->moverCursor(posicion_);
}
// Guardar texto que será reemplazado
size_t longitud_texto = texto_.length();
size_t inicio = editor_->getCursorPosicion();
size_t fin = inicio + longitud_texto;
texto_anterior_ = editor_->getContenido().substr(inicio, longitud_texto);
editor_->escribirTexto(texto_);
}
void undo() override {
// Remover el texto insertado y restaurar el anterior
size_t inicio = editor_->getCursorPosicion() - texto_.length();
size_t fin = editor_->getCursorPosicion();
const std::string& contenido = editor_->getContenido();
std::string nuevo_contenido = contenido.substr(0, inicio) +
texto_anterior_ +
contenido.substr(fin);
// Nota: No podemos modificar directamente el contenido, necesitamos un método
std::cout << "Deshaciendo inserción de '" << texto_ << "'" << std::endl;
}
};
class DeleteCommand : public EditorCommand {
private:
std::shared_ptr<TextEditor> editor_;
size_t cantidad_;
std::string texto_borrado_;
size_t posicion_original_;
public:
DeleteCommand(std::shared_ptr<TextEditor> editor, size_t cantidad = 1)
: editor_(editor), cantidad_(cantidad) {}
void execute() override {
posicion_original_ = editor_->getCursorPosicion();
texto_borrado_ = editor_->getContenido().substr(posicion_original_ - cantidad_, cantidad_);
editor_->borrarCaracter(cantidad_);
}
void undo() override {
std::cout << "Deshaciendo borrado de '" << texto_borrado_ << "'" << std::endl;
}
};
// Observadores concretos
class ConsoleObserver : public EditorObserver {
public:
void update(const std::string& event_type) override {
if (event_type == "contenido_cambiado") {
std::cout << "📝 Contenido actualizado" << std::endl;
} else if (event_type == "cursor_movido") {
std::cout << "📍 Cursor movido" << std::endl;
}
}
};
class FileObserver : public EditorObserver {
private:
std::string nombre_archivo_;
std::chrono::steady_clock::time_point ultimo_guardado_;
public:
FileObserver(const std::string& nombre_archivo) : nombre_archivo_(nombre_archivo) {
ultimo_guardado_ = std::chrono::steady_clock::now();
}
void update(const std::string& event_type) override {
if (event_type == "contenido_cambiado") {
auto ahora = std::chrono::steady_clock::now();
auto tiempo_transcurrido = std::chrono::duration_cast<std::chrono::seconds>(ahora - ultimo_guardado_).count();
if (tiempo_transcurrido > 5) { // Auto-guardar cada 5 segundos
std::cout << "💾 Auto-guardando en " << nombre_archivo_ << std::endl;
ultimo_guardado_ = ahora;
}
}
}
};
// Estrategias de guardado concretas
class LocalSaveStrategy : public SaveStrategy {
public:
void save(const std::string& contenido, const std::string& nombre_archivo) override {
std::cout << "💾 Guardando localmente en " << nombre_archivo << std::endl;
std::cout << " Contenido: " << contenido.substr(0, 50) << "..." << std::endl;
}
};
class CloudSaveStrategy : public SaveStrategy {
public:
void save(const std::string& contenido, const std::string& nombre_archivo) override {
std::cout << "☁️ Guardando en la nube: " << nombre_archivo << std::endl;
std::cout << " Subiendo " << contenido.length() << " caracteres..." << std::endl;
}
};
// Demo del editor completo
void demo_editor() {
std::cout << "=== Editor de Texto Interactivo con Múltiples Patrones ===" << std::endl;
// Crear editor
auto editor = std::make_shared<TextEditor>();
// Agregar observadores
auto observador_consola = std::make_shared<ConsoleObserver>();
auto observador_archivo = std::make_shared<FileObserver>("documento.txt");
editor->agregarObservador(observador_consola);
editor->agregarObservador(observador_archivo);
// Registrar estrategias de guardado
editor->registrarEstrategiaGuardado("local", std::make_unique<LocalSaveStrategy>());
editor->registrarEstrategiaGuardado("nube", std::make_unique<CloudSaveStrategy>());
editor->mostrarEstado();
// Simular entrada del usuario
std::vector<char> comandos = {
'i', // Entrar en modo inserción
'H', 'o', 'l', 'a', ' ', 'm', 'u', 'n', 'd', 'o',
27, // Escape - Volver a modo normal
'x', // Borrar carácter
'u', // Deshacer
};
for (char comando : comandos) {
std::cout << "\n🎹 Presionando tecla: '" << comando << "'" << std::endl;
editor->procesarTecla(comando);
editor->mostrarEstado();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "\n=== Estrategias de Guardado ===" << std::endl;
// Probar diferentes estrategias de guardado
editor->guardar("mi_documento.txt", "local");
editor->guardar("mi_documento.txt", "nube");
}
// Uso
int main() {
demo_editor();
return 0;
}
Conclusión
¡Has dominado los patrones de diseño de comportamiento en C++! Estos patrones te permiten gestionar algoritmos, estados y comunicaciones entre objetos de manera flexible, mantenible y eficiente, aprovechando las fortalezas únicas del lenguaje C++.
Ventajas de implementar patrones de comportamiento en C++
- Type Safety: El sistema de tipos fuerte de C++ detecta errores en tiempo de compilación
- Performance: Los patrones se ejecutan con overhead mínimo gracias al inlining y optimizaciones del compilador
- Memory Management: Control preciso sobre la memoria con RAII y smart pointers
- Multi-threading: Soporte nativo para concurrencia en patrones como Observer
- Templates: Permiten implementar patrones genéricos y reutilizables
- Zero-cost Abstractions: Los patrones bien diseñados no añaden overhead en runtime
Consejos para aplicar patrones de comportamiento en C++
- Empieza simple: No todos los problemas requieren patrones complejos
- Usa smart pointers:
std::shared_ptr,std::unique_ptrpara gestión automática de memoria - Aprovecha STL: Muchos patrones ya están implementados en la Standard Template Library
- Considera el ownership: Usa move semantics y RAII para recursos
- Piensa en thread safety: Especialmente para Observer y Mediator
- Usa templates: Para patrones genéricos y type-safe
- Evita memory leaks: Siempre limpia recursos apropiadamente
Practica aplicando estos patrones en proyectos reales y combina diferentes patrones según las necesidades específicas de tu aplicación. Recuerda que C++ te permite implementar estos patrones con un control y eficiencia que otros lenguajes no pueden igualar.
Para más tutoriales sobre patrones de diseño y arquitectura avanzada en C++, visita nuestra sección de tutoriales.
¡Sigue practicando y aplicando estos patrones en proyectos reales de C++!
💡 Tip Importante
📝 Mejores Prácticas en Patrones de Comportamiento con C++
- Elige el patrón adecuado: Analiza el problema de comunicación/algoritmo antes de seleccionar un patrón
- Usa smart pointers:
std::shared_ptrpara Observer,std::unique_ptrpara Strategy- Aprovecha RAII: Para cleanup automático en Command y Memento
- Implementa thread safety: Especialmente en Observer y Mediator
- Usa templates: Para patrones genéricos y type-safe
- Considera el ownership: Move semantics para eficiencia
- Evita memory leaks: Siempre usa RAII y smart pointers
- Testing: Los patrones deben facilitar el testing unitario
- Documenta decisiones: Explica por qué elegiste ciertos patrones en tu código
- Evita over-engineering: No uses patrones complejos para problemas simples
- Considera el rendimiento: Algunos patrones pueden afectar el rendimiento
- Usa STL cuando sea posible: Muchos patrones ya están implementados eficientemente
📚 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 - Behavioral Patterns - Ejemplos interactivos
🎯 Ejemplos de proyectos donde aplicar estos patrones:
- Observer: Sistemas de eventos, GUIs, sistemas reactivos
- Strategy: Algoritmos de ordenamiento, políticas de negocio
- Command: Editores de texto, sistemas de undo/redo
- State: Máquinas de estado, parsers, protocolos de red
- Template Method: Frameworks, algoritmos con pasos variables
- Iterator: Contenedores personalizados, lazy evaluation
- Mediator: Sistemas de chat, componentes GUI complejos
- Memento: Editores, sistemas de versionado, snapshots
¡Estos patrones te ayudarán a crear software C++ robusto, eficiente y mantenible!
No hay comentarios aún
Sé el primero en comentar este tutorial.