Padrão de Projeto Strategy em C++

O Padrão de Projeto Strategy é um padrão comportamental que permite definir uma família de algoritmos, encapsulá-los individualmente e torná-los intercambiáveis. Isso garante que o algoritmo possa variar independentemente do cliente que o utiliza. Essencialmente, vários algoritmos realizam a mesma tarefa, mas com implementações diferentes, e o cliente (ou Contexto) utiliza um desses algoritmos, resultando em comportamentos distintos.

Em comparação com o uso excessivo de estruturas condicionais como if...else, o Padrão Strategy reduz a complexidade e facilita a manutenção do código.

A principal desvantagem é que pode ser necessário definir um grande número de classes de Strategy, e todas elas precisam ser expostas ao cliente.

Quando usar o Padrão Strategy:

  • Quando existem várias classes relacionadas que diferem apenas em seu comportamento. O Padrão Strategy oferece uma maneira de configurar uma classe com um de vários comportamentos.
  • Quando é necessário usar diferentes variantes de um algoritmo.
  • Quando os dados usados pelo algoritmo não devem ser conhecidos pelo cliente. O Padrão Strategy pode ser usado para evitar a exposição de estruturas de dados complexas e relacionadas ao algoritmo.
  • Quando uma classe define muitos comportmaentos que aparecem como múltiplas instruções condicionais em suas operações. Mover os ramos condicionais relevantes para suas próprias classes Strategy substitui essas instruções condicionais.

Componentes do Padrão Strategy:

  • Strategy: Define uma interface comum para todos os algoritmos suportados. O Contexto utiliza esta interface para chamar o algoritmo definido por uma ConcreteStrategy.
  • ConcreteStrategy: Implementa o algoritmo específico que adere à interface Strategy.
  • Context: É configurado com um objeto ConcreteStrategy. Ele mantém uma referência a um objeto Strategy e pode definir uma interface para que a Strategy acesse seus dados.

Implementação Tradicional do Padrão Strategy em C++

Neste exemplo, diferentes métodos de ataque em um jogo são representados como estratégias distintas, e o personagem do jogo atua como o Contexto que executa essas estratégias.


#include <iostream>

// Interface Strategy: Define a ação de causar dano
class CausarDano {
public:
    virtual ~CausarDano() = default; // Destrutor virtual
    virtual void infligirDano() = 0;
};

// ConcreteStrategy: Dano de ataque básico
class DanoADC : public CausarDano {
public:
    void infligirDano() override {
        std::cout << "Dano de ADC: Perda de sangue!" << std::endl;
    }
};

// ConcreteStrategy: Dano de habilidade mágica
class DanoMagico : public CausarDano {
public:
    void infligirDano() override {
        std::cout << "Dano Magico: Perda de sangue!" << std::endl;
    }
};

// Context: Personagem do jogo (Guerreiro) que recebe uma estratégia de dano.
class Guerreiro {
public:
    // Construtor que recebe um ponteiro para a Strategy
    Guerreiro(CausarDano* estrategiaDano) : estrategia_dano_(estrategiaDano) {}

    ~Guerreiro() {
        delete estrategia_dano_; // Libera a memória da estratégia
    }

    // Executa a ação de ataque usando a estratégia configurada
    void atacar() {
        if (estrategia_dano_) {
            estrategia_dano_->infligirDano();
        }
    }

private:
    CausarDano* estrategia_dano_; // Ponteiro para a Strategy
};

// Context: Personagem do jogo (Mago) que seleciona a estratégia com base em um enum.
class Mago {
public:
    enum TipoDano {
        DANO_ADC,
        DANO_MAGICO,
        TIPO_DANO_INDEFINIDO
    };

    Mago(TipoDano tipo) {
        switch (tipo) {
            case DANO_ADC:
                estrategia_dano_ = new DanoADC();
                break;
            case DANO_MAGICO:
                estrategia_dano_ = new DanoMagico();
                break;
            default:
                estrategia_dano_ = nullptr; // Ou uma estratégia padrão
                break;
        }
    }

    ~Mago() {
        delete estrategia_dano_;
        std::cout << "~Mago() destruído." << std::endl;
    }

    void atacar() {
        if (estrategia_dano_) {
            estrategia_dano_->infligirDano();
        }
    }

private:
    CausarDano* estrategia_dano_;
};

// Context: Personagem do jogo (Arqueiro) que utiliza template para a Strategy.
template<typename T_Estrategia>
class Arqueiro {
public:
    // O ataque utiliza a instância da Strategy definida pelo template.
    void atacar() {
        estrategia_atual_.infligirDano();
    }
private:
    T_Estrategia estrategia_atual_; // Instância da Strategy
};

int main() {
    // Exemplo com Arqueiro usando DanoMagico como estratégia
    Arqueiro<DanoMagico>* arqueiro_mago = new Arqueiro<DanoMagico>;
    arqueiro_mago->atacar();
    delete arqueiro_mago;
    arqueiro_mago = nullptr;

    // Exemplo com Guerreiro escolhendo a estratégia de DanoADC
    Guerreiro* guerreiro = new Guerreiro(new DanoADC());
    guerreiro->atacar();
    delete guerreiro;
    guerreiro = nullptr;

    // Exemplo com Mago selecionando DanoMagico via enum
    Mago* mago = new Mago(Mago::DANO_MAGICO);
    mago->atacar();
    delete mago;
    mago = nullptr;

    return 0;
}

Implementação do Padrão Strategy com Ponteiros de Função

Esta abordagem utiliza ponteiros de função ou std::function para representar as estratégias, oferecendo uma alternativa mais leve em certos cenários.


#include <iostream>
#include <functional> // Para std::function

// Função de estratégia para dano ADC
void danoAdc() {
    std::cout << "Dano ADC" << std::endl;
}

// Função de estratégia para dano mágico
void danoApc() {
    std::cout << "Dano Magico" << std::endl;
}

// Contexto usando ponteiro de função tradicional
class GuerreiroComPonteiro {
public:
    // Define o tipo do ponteiro de função
    typedef void (*FuncaoDano)();

    GuerreiroComPonteiro(FuncaoDano func) : funcao_dano_(func) {}

    void atacar() {
        funcao_dano_(); // Chama a função de estratégia
    }

private:
    FuncaoDano funcao_dano_;
};

// Contexto usando std::function para maior flexibilidade
class MagoComStdFunction {
public:
    // Define o tipo usando std::function
    typedef std::function<void()> FuncaoDanoStd;

    MagoComStdFunction(FuncaoDanoStd func) : funcao_dano_(func) {}

    void atacar() {
        funcao_dano_(); // Chama a função de estratégia
    }

private:
    FuncaoDanoStd funcao_dano_;
};

int main() {
    // Exemplo com Guerreiro usando ponteiro de função
    GuerreiroComPonteiro* guerreiro = new GuerreiroComPonteiro(danoApc);
    guerreiro->atacar();
    delete guerreiro;
    guerreiro = nullptr;

    // Exemplo com Mago usando std::function
    MagoComStdFunction* mago = new MagoComStdFunction(danoAdc);
    mago->atacar();
    delete mago;
    mago = nullptr;

    return 0;
}

Tags: design patterns C++ Strategy Padrões de Projeto Programação Orientada a Objetos

Publicado em 10-15 16:01