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 alterar 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 que declara o método do algoritmo, Estratégias concretas que implementam diferentes variações e um Contexto que usa 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() na estratégia 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 vários 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 criar uma Calculadora de Frete que usa o padrão Strategy para calcular os custos de entrega com base em diferentes métodos de envio. Este é um cenário prático em que você precisa trocar algoritmos em tempo de execução: o mesmo pacote pode ser enviado por transporte terrestre, aéreo ou expresso, cada um com sua própria lógica de preços.
Você organizará seu código em três arquivos:
ShippingStrategy.h: Defina sua interface de estratégia e as estratégias concretas de envio.Crie uma classe abstrata
ShippingStrategycom um método virtual purocalculateCost(double weight)que retorna o custo de envio como um double, além de 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 usa uma estratégia de envio.Sua classe
ShippingServicedeve conter umstd::unique_ptr<ShippingStrategy>como 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 usa 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,airouexpress
Crie um
ShippingServicee defina a estratégia apropriada com base no método informado. Calcule e exiba o custo de envio.Em seguida, altere 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.Exiba cada custo em sua própria linha, com exatamente uma casa decimal, precedido pelo 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 ShippingService não precisa conhecer os detalhes de cada algoritmo de preços. Ele simplesmente delega para qualquer estratégia que esteja definida no momento. Você pode adicionar facilmente novos métodos de envio (como entrega por drone ou no mesmo dia) sem modificar a classe de serviço.
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;
// Definir 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. Defina a estratégia apropriada no serviço
// 2. Calcule e imprima o custo no formato: "[Method]: $[cost]"
// TODO: Troque para uma estratégia diferente:
// - Se a entrada foi "air", troque para GroundShipping
// - Caso contrário, troque para AirShipping
// Calcule e imprima 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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres 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)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online