Padrão Strategy
Parte da seção Programação Orientada a Objetos do Journey de PHP da Coddy — lição 74 de 91.
O Padrão Strategy é um padrão de projeto comportamental que permite definir uma família de algoritmos, encapsular cada um em sua própria classe e torná-los intercambiáveis em tempo de execução. O objeto que utiliza a estratégia não precisa saber qual algoritmo está executando.
Imagine um carrinho de compras que pode aplicar diferentes estratégias de desconto. Em vez de codificar rigidamente a lógica de desconto com condicionais, você define cada desconto como uma classe separada:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}
class NoDiscount implements DiscountStrategy {
public function calculate(float $total): float {
return $total;
}
}
class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function calculate(float $total): float {
return $total - ($total * $this->percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return max(0, $total - $this->amount);
}
}
A classe de contexto aceita qualquer estratégia e delega o cálculo:
<?php
class ShoppingCart {
public function __construct(private DiscountStrategy $discount) {}
public function setDiscount(DiscountStrategy $discount): void {
$this->discount = $discount;
}
public function checkout(float $total): float {
return $this->discount->calculate($total);
}
}
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);
Saída:
100
80O Padrão Strategy brilha quando você tem várias maneiras de realizar uma operação e deseja alternar entre elas sem modificar a classe que as utiliza. Adicionar um novo tipo de desconto significa criar uma nova classe, sem tocar no código existente.
Desafio
FácilVamos construir uma calculadora de custos de envio usando o Strategy Pattern. Diferentes métodos de envio têm diferentes regras de precificação — o envio padrão cobra uma taxa fixa, o envio expresso adiciona uma porcentagem e o envio noturno combina ambos. O Strategy Pattern permite que você troque esses cálculos sem alterar o código que os utiliza.
Você organizará seu código em quatro arquivos:
ShippingStrategy.php— Crie uma interfaceShippingStrategyque define o contrato para todos os métodos de envio. Ela deve ter um métodocalculate(float $weight): floatque recebe o peso do pacote e retorna o custo de envio.ShippingMethods.php— Inclua o arquivo ShippingStrategy e crie três classes que implementam a interface:StandardShipping— Cobra uma taxa fixa de$5.00independentemente do pesoExpressShipping— Cobra$2.00por quilograma (multiplique o peso por 2)OvernightShipping— Cobra uma taxa base de$10.00mais$3.00por quilograma
ShippingCalculator.php— Inclua o arquivo ShippingStrategy e crie uma classeShippingCalculatorque atua como o contexto. Sua calculadora deve:- Aceitar uma
ShippingStrategyem seu construtor (use promoção de construtor) - Ter um método
setStrategy(ShippingStrategy $strategy): voidpara alterar o método de envio em tempo de execução - Ter um método
calculateCost(float $weight): floatque delega para a estratégia atual
- Aceitar uma
main.php— Inclua os arquivos ShippingMethods e ShippingCalculator. Você receberá duas entradas: um tipo de envio ("standard","express", ou"overnight") e o peso de um pacote.Crie um
ShippingCalculatorcomeçando comStandardShipping. Com base na entrada do tipo de envio, usesetStrategy()para mudar para o método de envio apropriado. Em seguida, calcule o custo para o peso fornecido e exiba o resultado formatado com duas casas decimais.A saída deve ser:
Shipping cost: $[cost]
Este desafio demonstra como o Strategy Pattern torna seu sistema de envio flexível — adicionar um novo método de envio como SameDayShipping requer apenas a criação de uma nova classe que implementa a interface, sem tocar na calculadora ou nos métodos de envio existentes.
Folha de consulta
O Strategy Pattern (Padrão de Estratégia) é um padrão de design comportamental que define uma família de algoritmos, encapsula cada um em sua própria classe e os torna intercambiáveis em tempo de execução.
Defina uma interface para a estratégia:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}Implemente estratégias concretas:
<?php
class NoDiscount implements DiscountStrategy {
public function calculate(float $total): float {
return $total;
}
}
class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function calculate(float $total): float {
return $total - ($total * $this->percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return max(0, $total - $this->amount);
}
}Crie uma classe de contexto que utiliza a estratégia:
<?php
class ShoppingCart {
public function __construct(private DiscountStrategy $discount) {}
public function setDiscount(DiscountStrategy $discount): void {
$this->discount = $discount;
}
public function checkout(float $total): float {
return $this->discount->calculate($total);
}
}Use o padrão injetando estratégias:
<?php
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100); // 100
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100); // 80O Strategy Pattern permite que você alterne entre algoritmos sem modificar a classe que os utiliza. Adicionar novas estratégias requer apenas a criação de novas classes que implementem a interface.
Experimente você mesmo
<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';
// Ler entrada
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));
// TODO: Criar um ShippingCalculator começando com StandardShipping
// TODO: Com base na entrada do tipo de envio ("standard", "express" ou "overnight"),
// use setStrategy() para mudar para o método de envio apropriado
// TODO: Calcular o custo para o peso fornecido
// TODO: Imprimir o resultado formatado com duas casas decimais
// Formato de saída: Shipping cost: $[cost]
?>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 ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave $thisMétodosPropriedadesConstrutor (__construct)Destrutor (__destruct)Recapitulação - Calculadora Simples4Herança
Herança BásicaA Palavra-chave parent::Sobrescrita de MétodoA Palavra-chave finalClasses AbstratasRecapitulação - Hierarquia de Funcionários7Encapsulamento
Público, Protegido, PrivadoModificadores de Acesso em DetalhesGetters e SettersOcultação de InformaçãoPromoção de Construtor (8.0)Recapitulação - Sistema de Registro de Alunos10Conceitos Avançados de POO
Composição vs HerançaInjeção de DependênciaClasses AnônimasEnums (PHP 8.1)Fibers (PHP 8.1)Mergulho Profundo em Clonagem de ObjetosGeradores e Iteradores13Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Repository2Namespaces e Autoloading
Introdução a NamespacesA Palavra-chave usePadrão de Autoloading PSR-4Autoloader do ComposerRecapitulação - Projeto Organizado5Interfaces e Contratos
Introdução a InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesInterface vs Classe AbstrataType Hinting com InterfacesRecapitulação - Calculadora de Formas8Métodos Mágicos
Introdução aos Métodos Mágicos__toString e __debugInfo__get, __set, __isset, __unset__call e __callStatic__clone e Clonagem de Objetos__serialize e __unserializeRecapitulação - Coleção Personalizada11Sistema de Tipos e Tratamento de Erros
Declarações de TiposTipos AnuláveisTipos de União e InterseçãoClasses de ExceçãoHierarquia de Exceções PersonalizadasTry, Catch, FinallyRecapitulação - Validador de Formulário14Projeto: Gerenciamento de Biblioteca
Visão Geral do ProjetoClasses Livro e Usuário3Propriedades de Classe
Propriedades de Instância vs EstáticasConstantes em ClassesMétodos e Propriedades EstáticosPropriedades Privadas e ProtegidasPropriedades Readonly (PHP 8.1)Recapitulação - Gerenciador de Conta Bancária6Polimorfismo
Sobrescrita de Métodos RevisitadaPolimorfismo via InterfacesType Hinting e Union TypesLate Static BindingRecapitulação - Processador de Pagamentos9Traits
Introdução a TraitsUsando Múltiplos TraitsResolução de Conflitos de TraitsMétodos Abstratos em TraitsTraits vs Herança12Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão ObserverPadrão Strategy