Padrão Strategy
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 95 de 104.
O padrão Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Isso permite que você altere o comportamento de um objeto em tempo de execução sem modificar seu código - o algoritmo varia independentemente dos clientes que o utilizam.
O padrão consiste em três partes: uma interface Strategy declarando o método do algoritmo, Estratégias Concretas implementando diferentes variações, e um Contexto que utiliza uma estratégia:
#include <iostream>
#include <memory>
// Interface Strategy
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Estratégias concretas
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Contexto
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);
}O ShoppingCart não sabe qual método de pagamento está usando - ele apenas chama pay() em qualquer estratégia que esteja definida. Você pode trocar as estratégias em tempo de execução com setPaymentMethod(), tornando o sistema flexível e fácil de estender com novas opções de pagamento.
Use o Strategy quando você tiver múltiplos algoritmos para uma tarefa específica e quiser alternar entre eles dinamicamente, ou quando quiser evitar instruções condicionais para selecionar o comportamento.
Desafio
FácilVamos construir uma Calculadora de Frete que utiliza o padrão Strategy para calcular os custos de entrega com base em diferentes métodos de envio. Este é um cenário prático onde você precisa trocar algoritmos em tempo de execução — o mesmo pacote pode ser enviado via terrestre, aéreo ou expresso, cada um com sua própria lógica de precificação.
Você organizará seu código em três arquivos:
ShippingStrategy.h: Defina sua interface de estratégia e as estratégias de envio concretas.Crie uma classe abstrata
ShippingStrategycom um método virtual purocalculateCost(double weight)que retorna o custo de envio como um double, junto com um destrutor virtual.Em seguida, implemente três estratégias concretas:
GroundShipping— custa1.5por unidade de peso (weight * 1.5)AirShipping— custa4.0por unidade de peso (weight * 4.0)ExpressShipping— custa6.5por unidade de peso mais uma taxa fixa de10.0(weight * 6.5 + 10.0)
ShippingService.h: Crie a classe de contexto que utiliza uma estratégia de envio.Sua classe
ShippingServicedeve manter umstd::unique_ptr<ShippingStrategy>como um membro privado. Implemente:- Um método
setStrategy(std::unique_ptr<ShippingStrategy> strategy)para alterar o método de envio - Um método
calculateShipping(double weight)que utiliza a estratégia atual para calcular e retornar o custo
Se nenhuma estratégia estiver definida quando
calculateShippingfor chamado, retorne0.0.- Um método
main.cpp: Demonstre a troca de estratégias em tempo de execução.Leia duas entradas:
- Peso do pacote (double)
- Método de envio:
ground,air, ouexpress
Crie um
ShippingServicee defina a estratégia apropriada com base no método de entrada. Calcule e imprima o custo de envio.Em seguida, mude para uma estratégia diferente (use
airse a entrada não forair, caso contrário, useground) e calcule o custo novamente para o mesmo peso. Isso demonstra o poder de trocar estratégias em tempo de execução.Imprima cada custo em sua própria linha com exatamente uma casa decimal, prefixado com o nome do método:
[Method]: $[cost]
Por exemplo, com as entradas 5.0 e ground:
Ground: $7.5
Air: $20.0Com as entradas 3.0 e express:
Express: $29.5
Air: $12.0Com as entradas 10.0 e air:
Air: $40.0
Ground: $15.0Observe como o ShippingService não precisa conhecer os detalhes de cada algoritmo de precificação — ele simplesmente delega para qualquer estratégia que esteja definida no momento. Você pode facilmente adicionar novos métodos de envio (como entrega por drone ou no mesmo dia) sem modificar a classe de serviço.
Folha de consulta
O padrão Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Isso permite alterar o comportamento de um objeto em tempo de execução sem modificar seu código.
O padrão consiste em três partes:
- Interface Strategy: Uma classe abstrata que declara o método do algoritmo
- Strategies Concretas: Classes que implementam diferentes variações do algoritmo
- Contexto: Uma classe que usa uma estratégia e pode alternar entre elas
// Interface Strategy
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Estratégias concretas
class CreditCardPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via Credit Card\n";
}
};
class PayPalPayment : public PaymentStrategy {
public:
void pay(int amount) override {
std::cout << "Paid " << amount << " via PayPal\n";
}
};
// Contexto
class ShoppingCart {
std::unique_ptr<PaymentStrategy> strategy;
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
strategy = std::move(s);
}
void checkout(int total) {
if (strategy) strategy->pay(total);
}
};O contexto não sabe qual estratégia concreta está usando — ele apenas chama o método da interface. As estratégias podem ser trocadas em tempo de execução usando um método setter:
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);Quando usar: Use o Strategy quando você tiver múltiplos algoritmos para uma tarefa específica e quiser alternar entre eles dinamicamente, ou quando quiser evitar instruções condicionais para selecionar o comportamento.
Experimente você mesmo
#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"
int main() {
double weight;
std::string method;
std::cin >> weight;
std::cin >> method;
// Configura a saída para 1 casa decimal
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: Com base no método de entrada ("ground", "air", ou "express"):
// 1. Define a estratégia apropriada no serviço
// 2. Calcula e imprime o custo no formato: "[Method]: $[cost]"
// TODO: Alterna para uma estratégia diferente:
// - Se a entrada foi "air", alterna para GroundShipping
// - Caso contrário, alterna para AirShipping
// Calcula e imprime o novo custo
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários10Visão Geral da STL
Visão Geral e Filosofia da STLContainers da STLIteradoresAlgoritmos da STLFunctors e Expressões LambdaRecapitulação - Frequência de Palavras13Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico