Definir uma família de algoritmos, encapsular cada uma delas e torná-las intercambiáveis.
Strategy permite que o algoritmo varie (em tempo de execução) independentemente dos clientes que o utilizam.
Policy
Diferentes algoritmos são apropriados em diferentes situações;
Tornar mais fácil adicionar novos algoritmos e variar os existentes;
Facilitar a manutenção e entendimento.
Use o padrão Strategy quando:
muitas classes relacionadas diferem somente no seu comportamento. As estratégias fornecem uma maneira de configurar uma classe com um (dentre muitos) comportamentos;
você necessita de variantes de um algoritmo. Por exemplo, pode definir algoritmos que refletem diferentes soluções de compromisso entre espaço/ tempo. As estratégias podem ser usadas quando essas variantes são implementadas como uma hierarquia de classes de algoritmos;
um algoritmo usa dados dos quais os clientes não deveriam ter conhecimento. Use o padrão Strategy para evitar a exposição das estruturas de dados complexas, específicas do algoritmo;
uma classe define muitos comportamentos, e estes aparecem em suas operações como múltiplos comandos condicionais da linguagem. Em vez de usar muitos comandos condicionais, mova os ramos condicionais relacionados para a sua própria classe Strategy.
Metamodelo original Strategy
Modelo de aplicação Strategy
Strategy: define o contrato comum para todos os algoritmos suportados (ConcreteStrategy). Context usa a herança para chamar o algoritmo definido por uma ConcreteStrategy.
ConcreteStrategy: implementa o algoritmo usando o contrato de Strategy e adicionando novos atributos e operações.
Context: é configurado com um objeto ConcreteStrategy; mantém uma referência para um objeto Strategy.
Strategy e Context interagem para implementar o algoritmo escolhido.
Os clientes usualmente criam e passam um objeto ConcreteStrategy para o contexto; após isso, interagem exclusivamente com o contexto. Frequentemente, existe uma família de classes ConcreteStrategy para um cliente fazer sua escolha.
public abstract class Context {
Strategy strategy;
public void setBehavior(Strategy st) { strategy = st; }
public void behavior() { strategy.behavior(); }
}
public interface Strategy {
public void behavior();
}
public class ContextVariantA extends Context {
public ContextVariantA() {
/***
* strategy = new ConcreteStrategyA(); OU
* strategy = new ConcreteStrategyB(); OU
* strategy = new ConcreteStrategyN();
***/
/*
* O objeto de estratégia pode ser utilizado em qualquer local da
* ContextVariant. Não precisa ser no método construtor, como neste exemplo.
*/
}
}
public class ContextVariantB extends Context {
public ContextVariantB() {
/***
* strategy = new ConcreteStrategyA(); OU
* strategy = new ConcreteStrategyB(); OU
* strategy = new ConcreteStrategyN();
***/
/*
* O objeto de estratégia pode ser utilizado em qualquer local da
* ContextVariant. Não precisa ser no método construtor, como neste exemplo.
*/
}
}
public class ConcreteStrategyA implements Strategy {
public void behavior() { System.out.println("Comportamento com variação A"); }
}
public class ConcreteStrategyB implements Strategy {
public void behavior() { System.out.println("Comportamento com variação B"); }
}
Famílias de algoritmos relacionados. Hierarquias de classes Strategy definem uma família de algoritmos e comportamentos para os contextos reutilizarem. A herança pode ajudar a fatorar a funcionalidade comum dos algoritmos.
Uma alternativa ao uso de subclasses. Encapsular os algoritmos em classes Strategy separadas permite variar o algoritmo independentemente do seu contexto, tornando mais fácil trocá-los, compreendê-los e estendê-los.
Estratégias eliminam comandos condicionais da linguagem de programação. O padrão Strategy oferece uma alternativa ao uso de comandos condicionais para a seleção de comportamentos desejados.
A possibilidade de escolha de implementações. As estratégias podem fornecer diferentes implementações do mesmo comportamento.
Os clientes devem conhecer diferentes Strategies. O padrão tem uma deficiência potencial no fato de que um cliente deve compreender como Strategies diferem, antes que ele possa selecionar o mais apropriado. Os clientes podem ser expostos a detalhes e aspectos de implementação. Portanto, você deveria usar o padrão Strategy somente quando a variação em comportamento é relevante para os clientes.
Custo de comunicação entre Strategy e Context. A interface de Strategy é compartilhada por todas as classes ConcreteStrategy, quer os algoritmos que elas implementem sejam triviais ou complexos. Daí ser provável que alguns ConcreteStrategy não usem toda a informação passada através desta interface; ConcreteStrategies simples podem não usar quaisquer dessas informações! Isso significa que existirão situações em que o contexto criará e iniciará parâmetros que nunca serão usados.
Aumento do número de objetos. Strategies aumentam o número de objetos numa aplicação. Algumas vezes, você pode reduzir esse custo pela implementação de estratégias como objetos sem estados que os contextos possam compartilhar.
Flyweight: objetos Strategy geralmente são bons flyweights.
O padrão Strategy é uma forma de separar a lógica de negócio de uma classe da implementação específica de uma tarefa (comportamento).
Com este padrão, podemos encapsular algoritmos e torná-los intercambiáveis, permitindo que a classe utilize diferentes estratégias em tempo de execução.
Strategy envolve a criação de uma interface comum que representa uma estratégia e a implementação de várias classes que implementam essa interface com diferentes estratégias.
A classe que utiliza as diferentes estratégias contém uma referência a essa interface comum e pode alternar facilmente entre as diferentes estratégias conforme necessário.
Extrair Método
Extrair Classe
Para aplicar o padrão de projeto Strategy em um projeto, você pode seguir os seguintes passos:
Identificar a lógica de negócio referente a um comportamento que pode ser encapsulada e tornada intercambiável. Esta lógica pode ser uma operação matemática, um algoritmo de classificação, uma validação de entrada, etc.
Criar uma interface comum que represente a estratégia (Strategy). Esta interface deve conter um ou mais métodos que definem as operações que a estratégia realiza.
Criar várias classes que implementam essa interface (ConcreteStrategyA..N), cada uma com sua própria implementação específica da estratégia.
Adicionar um atributo à classe que utiliza a estratégia (Context) para armazenar uma referência à interface comum.
Adicionar um método à classe que utiliza a estratégia para permitir a troca da estratégia em tempo de execução (Context). Este método deve receber um objeto que implementa a interface comum e armazená-lo no campo criado no passo anterior.
Adicionar na classe que utiliza a estratégia uma chamada ao método da interface comum (Context), em vez de chamar diretamente a implementação específica da estratégia.
Testar a funcionalidade da classe com diferentes estratégias, verificando se a troca da estratégia em tempo de execução está funcionando corretamente.
Refatorar o código, se necessário, para garantir que o padrão Strategy esteja sendo aplicado corretamente e de forma clara.
// Passo 2: Criar uma interface comum que represente a estratégia.
// Interface comum para as diferentes estratégias de operações matemáticas
interface StrategyOperacao {
int behavior(int a, int b);
}
// Classe que utiliza diferentes estratégias
class Calculadora {
// Passo 4: Adicionar um atributo à classe que utiliza a estratégia para armazenar uma referência à interface comum.
private StrategyOperacao strategy;
// Passo 5: Adicionar um método à classe que utiliza a estratégia para permitir a troca da estratégia em tempo de execução.
public void setBehavior(StrategyOperacao strategy) {
this.strategy = strategy;
}
// Passo 6: Adicionar na classe que utiliza a estratégia uma chamada ao método da interface comum.
public int behavior(int a, int b) {
return strategy.behavior(a, b);
}
}
// Passo 3: Criar várias classes que implementam essa interface com diferentes estratégias.
// Implementação de estratégia que realiza a operação de adição
class ConcreteStrategySomar implements StrategyOperacao {
public int behavior(int a, int b) {
return a + b;
}
}
// Passo 3: Criar várias classes que implementam essa interface com diferentes estratégias.
// Implementação de estratégia que realiza a operação de subtração
class ConcreteStrategySubtrair implements StrategyOperacao {
public int behavior(int a, int b) {
return a - b;
}
}
// Passo 7: Testar a funcionalidade da classe com diferentes estratégias, verificando se a troca da estratégia em tempo de execução está funcionando corretamente.
public class Main {
public static void main(String[] args) {
Calculadora calculadora = new Calculadora();
calculadora.setBehavior(new ConcreteStrategySomar());
System.out.println(calculadora.behavior(5, 3)); // imprime 8
calculadora.setBehavior(new ConcreteStrategySubtrair());
System.out.println(calculadora.behavior(5, 3)); // imprime 2
}
}