Los patrones de diseño son soluciones reutilizables a problemas comunes que surgen en el desarrollo de software. Estos patrones no son soluciones completas, sino plantillas que pueden ser aplicadas a problemas específicos en diferentes contextos. En este módulo, exploraremos algunos de los patrones de diseño más utilizados en C++.

Objetivos del Módulo

  • Comprender qué son los patrones de diseño y por qué son importantes.
  • Aprender sobre los patrones de diseño más comunes: Singleton, Factory, Observer, y Strategy.
  • Implementar ejemplos prácticos de cada patrón en C++.
  • Aplicar estos patrones en situaciones reales para mejorar la estructura y mantenibilidad del código.

Contenido

Introducción a los Patrones de Diseño

¿Qué son los Patrones de Diseño?

Los patrones de diseño son soluciones probadas y documentadas para problemas recurrentes en el diseño de software. Estos patrones ayudan a crear software más flexible, reutilizable y fácil de mantener.

Clasificación de los Patrones de Diseño

Los patrones de diseño se dividen en tres categorías principales:

  • Patrones Creacionales: Se enfocan en la creación de objetos.
  • Patrones Estructurales: Se centran en la composición de clases y objetos.
  • Patrones de Comportamiento: Se ocupan de la interacción y responsabilidad entre objetos.

Patrón Singleton

Descripción

El patrón Singleton asegura que una clase tenga una única instancia y proporciona un punto de acceso global a ella.

Implementación en C++

#include <iostream>

class Singleton {
private:
    static Singleton* instance;
    int data;

    // Constructor privado para evitar instanciación directa
    Singleton() : data(0) {}

public:
    // Método estático para obtener la instancia única
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

    void setData(int value) {
        data = value;
    }

    int getData() {
        return data;
    }
};

// Inicialización del puntero estático
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    s1->setData(100);

    Singleton* s2 = Singleton::getInstance();
    std::cout << "Data: " << s2->getData() << std::endl; // Output: Data: 100

    return 0;
}

Explicación

  • Constructor Privado: Evita que se creen instancias directamente.
  • Método getInstance: Proporciona un punto de acceso global a la instancia única.
  • Puntero Estático instance: Almacena la única instancia de la clase.

Patrón Factory

Descripción

El patrón Factory define una interfaz para crear un objeto, pero deja que las subclases decidan qué clase instanciar. Permite a una clase delegar la responsabilidad de creación a subclases.

Implementación en C++

#include <iostream>
#include <string>

class Product {
public:
    virtual void use() = 0;
};

class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "Using Product A" << std::endl;
    }
};

class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "Using Product B" << std::endl;
    }
};

class Factory {
public:
    static Product* createProduct(const std::string& type) {
        if (type == "A") {
            return new ConcreteProductA();
        } else if (type == "B") {
            return new ConcreteProductB();
        } else {
            return nullptr;
        }
    }
};

int main() {
    Product* productA = Factory::createProduct("A");
    productA->use(); // Output: Using Product A

    Product* productB = Factory::createProduct("B");
    productB->use(); // Output: Using Product B

    delete productA;
    delete productB;

    return 0;
}

Explicación

  • Clase Product: Define una interfaz común para los productos.
  • Clases ConcreteProductA y ConcreteProductB: Implementan la interfaz Product.
  • Clase Factory: Contiene el método createProduct que decide qué producto crear basado en el tipo.

Patrón Observer

Descripción

El patrón Observer define una dependencia uno-a-muchos entre objetos, de manera que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.

Implementación en C++

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

class Observer {
public:
    virtual void update(const std::string& message) = 0;
};

class ConcreteObserver : public Observer {
private:
    std::string name;
public:
    ConcreteObserver(const std::string& name) : name(name) {}

    void update(const std::string& message) override {
        std::cout << name << " received: " << message << std::endl;
    }
};

class Subject {
private:
    std::vector<Observer*> observers;
public:
    void addObserver(Observer* observer) {
        observers.push_back(observer);
    }

    void removeObserver(Observer* observer) {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void notifyObservers(const std::string& message) {
        for (Observer* observer : observers) {
            observer->update(message);
        }
    }
};

int main() {
    Subject subject;
    ConcreteObserver observer1("Observer 1");
    ConcreteObserver observer2("Observer 2");

    subject.addObserver(&observer1);
    subject.addObserver(&observer2);

    subject.notifyObservers("Event 1"); // Output: Observer 1 received: Event 1
                                        //         Observer 2 received: Event 1

    subject.removeObserver(&observer1);
    subject.notifyObservers("Event 2"); // Output: Observer 2 received: Event 2

    return 0;
}

Explicación

  • Clase Observer: Define una interfaz para los observadores.
  • Clase ConcreteObserver: Implementa la interfaz Observer.
  • Clase Subject: Mantiene una lista de observadores y notifica a todos cuando ocurre un evento.

Patrón Strategy

Descripción

El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan.

Implementación en C++

#include <iostream>

class Strategy {
public:
    virtual void execute() = 0;
};

class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        std::cout << "Executing Strategy A" << std::endl;
    }
};

class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        std::cout << "Executing Strategy B" << std::endl;
    }
};

class Context {
private:
    Strategy* strategy;
public:
    Context(Strategy* strategy) : strategy(strategy) {}

    void setStrategy(Strategy* strategy) {
        this->strategy = strategy;
    }

    void executeStrategy() {
        strategy->execute();
    }
};

int main() {
    ConcreteStrategyA strategyA;
    ConcreteStrategyB strategyB;

    Context context(&strategyA);
    context.executeStrategy(); // Output: Executing Strategy A

    context.setStrategy(&strategyB);
    context.executeStrategy(); // Output: Executing Strategy B

    return 0;
}

Explicación

  • Clase Strategy: Define una interfaz común para todas las estrategias.
  • Clases ConcreteStrategyA y ConcreteStrategyB: Implementan la interfaz Strategy.
  • Clase Context: Utiliza una estrategia y permite cambiarla en tiempo de ejecución.

Ejercicios Prácticos

Ejercicio 1: Implementar el Patrón Singleton

Implementa una clase Logger utilizando el patrón Singleton. La clase debe tener un método log que imprima mensajes en la consola.

Ejercicio 2: Implementar el Patrón Factory

Crea una fábrica que pueda crear diferentes tipos de vehículos (por ejemplo, Car y Bike). Cada vehículo debe tener un método drive que imprima un mensaje específico.

Ejercicio 3: Implementar el Patrón Observer

Implementa un sistema de notificaciones donde diferentes usuarios pueden suscribirse a un canal y recibir mensajes cuando se publique un nuevo contenido.

Ejercicio 4: Implementar el Patrón Strategy

Crea una aplicación de cálculo que pueda cambiar entre diferentes estrategias de cálculo (por ejemplo, suma, resta, multiplicación).

Conclusión

En este módulo, hemos explorado algunos de los patrones de diseño más comunes y su implementación en C++. Los patrones de diseño son herramientas poderosas que pueden mejorar significativamente la calidad y mantenibilidad de tu código. A medida que avances en tu carrera como desarrollador, encontrarás que estos patrones son esenciales para resolver problemas complejos de manera eficiente y elegante.

¡Felicidades por completar este módulo! Ahora estás listo para aplicar estos patrones en tus propios proyectos y seguir avanzando en tu aprendizaje de C++.

© Copyright 2024. Todos los derechos reservados