Strategy
Intenção (GoF)
Seção intitulada “Intenção (GoF)”Definir uma família de algoritmos, encapsular cada um deles e torná-los intercambiáveis. O Strategy permite que o algoritmo varie independentemente dos clientes que o utilizam.
Problema
Seção intitulada “Problema”Uma classe possui um comportamento que varia conforme condições, e essa variação é codificada como um bloco de condicionais (if/else, switch). A cada nova variante, edita-se a classe — violando o OCP, inchando o método, misturando responsabilidades (SRP - Single Responsibility Principle) e dificultando teste isolado de cada ramo.
Estrutura
Seção intitulada “Estrutura”- Strategy: interface que declara a operação comum a todos os algoritmos.
- ConcreteStrategy: cada implementação encapsula um algoritmo específico.
- Context: mantém uma referência a uma Strategy e delega a ela, sem conhecer a implementação concreta — recebe a estratégia por composição (idealmente por injeção, apoiando o DIP - Dependency Inversion Principle).
O ponto essencial: o Context depende da abstração, não do algoritmo concreto. Trocar a estratégia é trocar o objeto injetado.
Ponto-chave: OCP, refactoring e DIP
Seção intitulada “Ponto-chave: OCP, refactoring e DIP”- Materialização do OCP: adicionar um novo algoritmo é criar uma nova classe/lambda que implementa a interface — sem tocar no Context nem nas estratégias existentes. O sistema fica aberto para extensão e fechado para modificação.
- Destino do refactoring “Replace Conditional with Polymorphism” (ver Técnicas de Refactoring): um
switch (tipo)espalhado vira um conjunto de estratégias polimórficas selecionadas por um mapa ou factory. É a cura de vários Code Smells (Switch Statements, Long Method), tema tratado em Code Smells e SOLID. - Apoia o DIP - Dependency Inversion Principle: o Context depende de uma abstração (
Strategy), e as políticas concretas são fornecidas de fora, invertendo a dependência.
Consequências e trade-offs
Seção intitulada “Consequências e trade-offs”Prós
- Elimina condicionais de seleção de algoritmo.
- Cada algoritmo é testável isoladamente.
- Troca em runtime; composição em vez de herança.
Contras
- Mais classes/objetos e uma indireção a mais.
- O cliente precisa conhecer as estratégias disponíveis para escolher (mitigável com factory/registry).
- Overhead injustificado se as variações são poucas, estáveis e triviais.
Quando NÃO usar
Seção intitulada “Quando NÃO usar”Quando há uma única implementação sem variação prevista, ou quando a diferença entre “algoritmos” é tão pequena que um parâmetro simples resolve. Não crie uma hierarquia de estratégias para dois ifs que nunca vão crescer.
Exemplo em Java
Seção intitulada “Exemplo em Java”Antes: condicional de seleção
Seção intitulada “Antes: condicional de seleção”public class CheckoutService {
public BigDecimal computeDiscount(String customerType, BigDecimal amount) { if (customerType.equals("REGULAR")) { return BigDecimal.ZERO; } else if (customerType.equals("PREMIUM")) { return amount.multiply(new BigDecimal("0.10")); } else if (customerType.equals("VIP")) { return amount.multiply(new BigDecimal("0.20")); } throw new IllegalArgumentException("tipo desconhecido"); }}Cada novo tipo de cliente exige editar este método.
Depois: estratégias intercambiáveis
Seção intitulada “Depois: estratégias intercambiáveis”@FunctionalInterfacepublic interface DiscountStrategy { BigDecimal discountFor(BigDecimal amount);}
public class RegularDiscount implements DiscountStrategy { public BigDecimal discountFor(BigDecimal amount) { return BigDecimal.ZERO; }}
public class PremiumDiscount implements DiscountStrategy { public BigDecimal discountFor(BigDecimal amount) { return amount.multiply(new BigDecimal("0.10")); }}
public class VipDiscount implements DiscountStrategy { public BigDecimal discountFor(BigDecimal amount) { return amount.multiply(new BigDecimal("0.20")); }}Context por composição/injeção:
public class CheckoutService {
private final DiscountStrategy strategy;
public CheckoutService(DiscountStrategy strategy) { this.strategy = strategy; }
public BigDecimal finalPrice(BigDecimal amount) { return amount.subtract(strategy.discountFor(amount)); }}Seleção desacoplada via registry:
public class DiscountResolver {
private final Map<String, DiscountStrategy> strategies = Map.of( "REGULAR", new RegularDiscount(), "PREMIUM", new PremiumDiscount(), "VIP", new VipDiscount() );
public DiscountStrategy resolve(String customerType) { DiscountStrategy s = strategies.get(customerType); if (s == null) { throw new IllegalArgumentException("tipo desconhecido: " + customerType); } return s; }}Strategy como lambda
Seção intitulada “Strategy como lambda”Como DiscountStrategy é uma interface funcional, estratégias simples dispensam classes:
DiscountStrategy blackFriday = amount -> amount.multiply(new BigDecimal("0.35"));CheckoutService service = new CheckoutService(blackFriday);Em Java moderno, muitas Strategies triviais viram lambdas ou method references, reduzindo a cerimônia do padrão à sua essência: um algoritmo passado como valor.
Diagrama
Seção intitulada “Diagrama”classDiagram
class DiscountStrategy {
<<interface>>
+discountFor(amount) BigDecimal
}
class RegularDiscount {
+discountFor(amount) BigDecimal
}
class PremiumDiscount {
+discountFor(amount) BigDecimal
}
class VipDiscount {
+discountFor(amount) BigDecimal
}
class CheckoutService {
-DiscountStrategy strategy
+finalPrice(amount) BigDecimal
}
DiscountStrategy <|.. RegularDiscount
DiscountStrategy <|.. PremiumDiscount
DiscountStrategy <|.. VipDiscount
CheckoutService o--> DiscountStrategy
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Vazar a seleção da estratégia para dentro do Context com um
switchque escolhe a strategy — isso só empurra a condicional para outro lugar. Centralize a escolha num factory/registry ou injete diretamente. - Estratégias com interfaces divergentes, forçando
instanceofno cliente — quebra a intercambialidade. - Confundir com State: mesma UML, intenção diferente. Strategy é escolhida de fora e não conhece as outras; State controla suas transições.
- Aplicar o padrão para uma variação única e estável — indireção sem retorno.
Relações
Seção intitulada “Relações”- Padrão comportamental de Padrões Comportamentais / Design Patterns (GoF).
- Materializa o OCP e apoia o DIP - Dependency Inversion Principle; parte do conjunto SOLID.
- Destino do refactoring Replace Conditional with Polymorphism — ver Técnicas de Refactoring e Code Smells e SOLID.
- Primo estrutural de State; alternativa por composição ao Template Method.
- Índice: Design Patterns (GoF) · Catálogo de Patterns.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Por que se diz que Strategy é a materialização do OCP?
Resposta
Porque adicionar um novo algoritmo é criar uma nova implementação da interface Strategy, sem modificar o Context nem as estratégias existentes. O sistema fica aberto para extensão (novas estratégias) e fechado para modificação (código estável), que é exatamente o enunciado do OCP.
2. Qual refactoring conduz naturalmente ao Strategy e qual smell ele cura?
Resposta
Replace Conditional with Polymorphism: um switch/if-else que seleciona comportamento por tipo vira estratégias polimórficas. Ataca os smells Switch Statements e Long Method.
3. Como o Strategy apoia o DIP?
Resposta
O Context depende da abstração Strategy, não de algoritmos concretos, e recebe a estratégia por injeção. A dependência aponta para a interface (política), invertendo o acoplamento em relação às implementações de baixo nível.
4. Quando uma Strategy pode ser apenas uma lambda em Java?
Resposta
Quando a interface da estratégia é funcional (um único método abstrato). Estratégias simples e sem estado dispensam classes concretas e podem ser expressas como lambdas ou method references, reduzindo boilerplate sem perder a intenção do padrão.