Els patrons de disseny són solucions provades i documentades per a problemes comuns en el desenvolupament de programari. Aquests patrons ajuden a crear codi més reutilitzable, mantenible i escalable. En aquest tema, explorarem alguns dels patrons de disseny més comuns i com implementar-los en C++.

Continguts

Introducció als Patrons de Disseny

Els patrons de disseny es poden classificar en tres categories principals:

  • Patrons de Creació: Ajuden a crear objectes de manera que es controlin millor les complexitats de la creació.
  • Patrons Estructurals: Ajuden a compondre objectes i classes en estructures més grans.
  • Patrons de Comportament: Ajuden a definir com els objectes interactuen i es comuniquen entre ells.

Patrons de Creació

Singleton

El patró Singleton assegura que una classe només tingui una instància i proporciona un punt d'accés global a aquesta instància.

Exemple de Codi

class Singleton {
private:
    static Singleton* instance;
    Singleton() {} // Constructor privat

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// Inicialització del punter estàtic
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    if (s1 == s2) {
        std::cout << "Les dues instàncies són iguals." << std::endl;
    }

    return 0;
}

Factory Method

El patró Factory Method defineix una interfície per crear un objecte, però permet a les subclasses decidir quina classe instanciar.

Exemple de Codi

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 Creator {
public:
    virtual Product* factoryMethod() = 0;
};

class ConcreteCreatorA : public Creator {
public:
    Product* factoryMethod() override {
        return new ConcreteProductA();
    }
};

class ConcreteCreatorB : public Creator {
public:
    Product* factoryMethod() override {
        return new ConcreteProductB();
    }
};

int main() {
    Creator* creatorA = new ConcreteCreatorA();
    Product* productA = creatorA->factoryMethod();
    productA->use();

    Creator* creatorB = new ConcreteCreatorB();
    Product* productB = creatorB->factoryMethod();
    productB->use();

    delete productA;
    delete productB;
    delete creatorA;
    delete creatorB;

    return 0;
}

Abstract Factory

El patró Abstract Factory proporciona una interfície per crear famílies d'objectes relacionats o dependents sense especificar les seves classes concretes.

Exemple de Codi

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

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

class ConcreteProductA1 : public AbstractProductA {
public:
    void use() override {
        std::cout << "Using Product A1" << std::endl;
    }
};

class ConcreteProductA2 : public AbstractProductA {
public:
    void use() override {
        std::cout << "Using Product A2" << std::endl;
    }
};

class ConcreteProductB1 : public AbstractProductB {
public:
    void use() override {
        std::cout << "Using Product B1" << std::endl;
    }
};

class ConcreteProductB2 : public AbstractProductB {
public:
    void use() override {
        std::cout << "Using Product B2" << std::endl;
    }
};

class AbstractFactory {
public:
    virtual AbstractProductA* createProductA() = 0;
    virtual AbstractProductB* createProductB() = 0;
};

class ConcreteFactory1 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA1();
    }
    AbstractProductB* createProductB() override {
        return new ConcreteProductB1();
    }
};

class ConcreteFactory2 : public AbstractFactory {
public:
    AbstractProductA* createProductA() override {
        return new ConcreteProductA2();
    }
    AbstractProductB* createProductB() override {
        return new ConcreteProductB2();
    }
};

int main() {
    AbstractFactory* factory1 = new ConcreteFactory1();
    AbstractProductA* productA1 = factory1->createProductA();
    AbstractProductB* productB1 = factory1->createProductB();
    productA1->use();
    productB1->use();

    AbstractFactory* factory2 = new ConcreteFactory2();
    AbstractProductA* productA2 = factory2->createProductA();
    AbstractProductB* productB2 = factory2->createProductB();
    productA2->use();
    productB2->use();

    delete productA1;
    delete productB1;
    delete productA2;
    delete productB2;
    delete factory1;
    delete factory2;

    return 0;
}

Patrons Estructurals

Adapter

El patró Adapter permet que classes amb interfícies incompatibles treballin juntes.

Exemple de Codi

class Target {
public:
    virtual void request() {
        std::cout << "Target request" << std::endl;
    }
};

class Adaptee {
public:
    void specificRequest() {
        std::cout << "Adaptee specific request" << std::endl;
    }
};

class Adapter : public Target {
private:
    Adaptee* adaptee;

public:
    Adapter(Adaptee* a) : adaptee(a) {}

    void request() override {
        adaptee->specificRequest();
    }
};

int main() {
    Adaptee* adaptee = new Adaptee();
    Target* target = new Adapter(adaptee);
    target->request();

    delete adaptee;
    delete target;

    return 0;
}

Decorator

El patró Decorator permet afegir funcionalitats addicionals a un objecte de manera dinàmica.

Exemple de Codi

class Component {
public:
    virtual void operation() = 0;
};

class ConcreteComponent : public Component {
public:
    void operation() override {
        std::cout << "ConcreteComponent operation" << std::endl;
    }
};

class Decorator : public Component {
protected:
    Component* component;

public:
    Decorator(Component* c) : component(c) {}

    void operation() override {
        component->operation();
    }
};

class ConcreteDecoratorA : public Decorator {
public:
    ConcreteDecoratorA(Component* c) : Decorator(c) {}

    void operation() override {
        Decorator::operation();
        std::cout << "ConcreteDecoratorA operation" << std::endl;
    }
};

class ConcreteDecoratorB : public Decorator {
public:
    ConcreteDecoratorB(Component* c) : Decorator(c) {}

    void operation() override {
        Decorator::operation();
        std::cout << "ConcreteDecoratorB operation" << std::endl;
    }
};

int main() {
    Component* component = new ConcreteComponent();
    Component* decoratorA = new ConcreteDecoratorA(component);
    Component* decoratorB = new ConcreteDecoratorB(decoratorA);
    decoratorB->operation();

    delete decoratorB;
    delete decoratorA;
    delete component;

    return 0;
}

Facade

El patró Facade proporciona una interfície simplificada a un conjunt de subsistemes.

Exemple de Codi

class SubsystemA {
public:
    void operationA() {
        std::cout << "SubsystemA operation" << std::endl;
    }
};

class SubsystemB {
public:
    void operationB() {
        std::cout << "SubsystemB operation" << std::endl;
    }
};

class Facade {
private:
    SubsystemA* subsystemA;
    SubsystemB* subsystemB;

public:
    Facade() {
        subsystemA = new SubsystemA();
        subsystemB = new SubsystemB();
    }

    ~Facade() {
        delete subsystemA;
        delete subsystemB;
    }

    void operation() {
        subsystemA->operationA();
        subsystemB->operationB();
    }
};

int main() {
    Facade* facade = new Facade();
    facade->operation();

    delete facade;

    return 0;
}

Patrons de Comportament

Observer

El patró Observer defineix una dependència un-a-molts entre objectes de manera que quan un objecte canvia d'estat, tots els seus dependents són notificats i actualitzats automàticament.

Exemple de Codi

#include <vector>
#include <algorithm>

class Observer {
public:
    virtual void update() = 0;
};

class Subject {
private:
    std::vector<Observer*> observers;

public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

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

    void notify() {
        for (Observer* observer : observers) {
            observer->update();
        }
    }
};

class ConcreteObserver : public Observer {
private:
    Subject& subject;

public:
    ConcreteObserver(Subject& s) : subject(s) {
        subject.attach(this);
    }

    ~ConcreteObserver() {
        subject.detach(this);
    }

    void update() override {
        std::cout << "Observer updated" << std::endl;
    }
};

int main() {
    Subject subject;
    ConcreteObserver observer1(subject);
    ConcreteObserver observer2(subject);

    subject.notify();

    return 0;
}

Strategy

El patró Strategy permet definir una família d'algoritmes, encapsular-los i fer-los intercanviables.

Exemple de Codi

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* s) : strategy(s) {}

    void setStrategy(Strategy* s) {
        strategy = s;
    }

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

int main() {
    Strategy* strategyA = new ConcreteStrategyA();
    Strategy* strategyB = new ConcreteStrategyB();

    Context context(strategyA);
    context.executeStrategy();

    context.setStrategy(strategyB);
    context.executeStrategy();

    delete strategyA;
    delete strategyB;

    return 0;
}

Command

El patró Command encapsula una petició com un objecte, permetent així parametritzar clients amb diferents peticions, posar en cua o registrar peticions i suportar operacions reversibles.

Exemple de Codi

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

class Receiver {
public:
    void action() {
        std::cout << "Receiver action" << std::endl;
    }
};

class ConcreteCommand : public Command {
private:
    Receiver* receiver;

public:
    ConcreteCommand(Receiver* r) : receiver(r) {}

    void execute() override {
        receiver->action();
    }
};

class Invoker {
private:
    Command* command;

public:
    void setCommand(Command* c) {
        command = c;
    }

    void executeCommand() {
        command->execute();
    }
};

int main() {
    Receiver* receiver = new Receiver();
    Command* command = new ConcreteCommand(receiver);
    Invoker invoker;
    invoker.setCommand(command);
    invoker.executeCommand();

    delete command;
    delete receiver;

    return 0;
}

Exercicis Pràctics

  1. Implementa el patró Singleton: Crea una classe Logger que només permeti una instància i proporcioni un mètode per escriure missatges de registre.
  2. Utilitza el patró Factory Method: Crea una aplicació que utilitzi el patró Factory Method per crear diferents tipus de documents (PDF, Word, etc.).
  3. Aplica el patró Observer: Implementa un sistema de notificacions on diversos observadors reben actualitzacions quan es produeix un canvi en un subjecte.

Conclusió

Els patrons de disseny són eines poderoses que poden millorar significativament la qualitat del teu codi. En aquest tema, hem explorat alguns dels patrons de disseny més comuns i com implementar-los en C++. Practicar aquests patrons t'ajudarà a escriure codi més net, mantenible i escalable.

© Copyright 2024. Tots els drets reservats