Padrão de estratégia - Strategy pattern
Na programação de computadores , o padrão de estratégia (também conhecido como padrão de política ) é um padrão de design de software comportamental que permite selecionar um algoritmo em tempo de execução. Em vez de implementar um único algoritmo diretamente, o código recebe instruções em tempo de execução sobre quais algoritmos usar em uma família.
A estratégia permite que o algoritmo varie independentemente dos clientes que o utilizam. Estratégia é um dos padrões incluídos no influente livro Design Patterns de Gamma et al. que popularizou o conceito de usar padrões de projeto para descrever como projetar software orientado a objetos flexível e reutilizável. Adiar a decisão sobre qual algoritmo usar até o tempo de execução permite que o código de chamada seja mais flexível e reutilizável.
Por exemplo, uma classe que realiza validação em dados recebidos pode usar o padrão de estratégia para selecionar um algoritmo de validação dependendo do tipo de dados, a fonte dos dados, a escolha do usuário ou outros fatores discriminantes. Esses fatores não são conhecidos até o tempo de execução e podem exigir uma validação radicalmente diferente para ser executada. Os algoritmos (estratégias) de validação, encapsulados separadamente do objeto de validação, podem ser usados por outros objetos de validação em diferentes áreas do sistema (ou mesmo em sistemas diferentes) sem duplicação de código .
Normalmente, o padrão de estratégia armazena uma referência a algum código em uma estrutura de dados e a recupera. Isto pode ser conseguido através de mecanismos como o nativo ponteiro de função , a função de primeira classe , classes ou instâncias de classe em programação orientada a objeto linguagens, ou acessar o armazenamento interno do implementação da linguagem de código via reflexão .
Estrutura
Classe UML e diagrama de sequência
No diagrama de classes UML acima , a Context
classe não implementa um algoritmo diretamente. Em vez disso, Context
refere-se à Strategy
interface para executar um algoritmo ( strategy.algorithm()
), que Context
independe de como um algoritmo é implementado. As classes Strategy1
e Strategy2
implementam a Strategy
interface, ou seja, implementam (encapsulam) um algoritmo.
O diagrama de sequência UML
mostra as interações em tempo de execução: O Context
objeto delega um algoritmo para diferentes Strategy
objetos. Primeiro, Context
chama algorithm()
um Strategy1
objeto, que executa o algoritmo e retorna o resultado para Context
. Depois disso, Context
muda sua estratégia e chama algorithm()
um Strategy2
objeto, que executa o algoritmo e retorna o resultado para Context
.
Diagrama de classes
Exemplo
C #
O exemplo a seguir está em C # .
public class StrategyPatternWiki
{
public static void Main(String[] args)
{
// Prepare strategies
var normalStrategy = new NormalStrategy();
var happyHourStrategy = new HappyHourStrategy();
var firstCustomer = new CustomerBill(normalStrategy);
// Normal billing
firstCustomer.Add(1.0, 1);
// Start Happy Hour
firstCustomer.Strategy = happyHourStrategy;
firstCustomer.Add(1.0, 2);
// New Customer
var secondCustomer = new CustomerBill(happyHourStrategy);
secondCustomer.Add(0.8, 1);
// The Customer pays
firstCustomer.Print();
// End Happy Hour
secondCustomer.Strategy = normalStrategy;
secondCustomer.Add(1.3, 2);
secondCustomer.Add(2.5, 1);
secondCustomer.Print();
}
}
// CustomerBill as class name since it narrowly pertains to a customer's bill
class CustomerBill
{
private IList<double> drinks;
// Get/Set Strategy
public IBillingStrategy Strategy { get; set; }
public CustomerBill(IBillingStrategy strategy)
{
this.drinks = new List<double>();
this.Strategy = strategy;
}
public void Add(double price, int quantity)
{
this.drinks.Add(this.Strategy.GetActPrice(price * quantity));
}
// Payment of bill
public void Print()
{
double sum = 0;
foreach (var drinkCost in this.drinks)
{
sum += drinkCost;
}
Console.WriteLine($"Total due: {sum}.");
this.drinks.Clear();
}
}
interface IBillingStrategy
{
double GetActPrice(double rawPrice);
}
// Normal billing strategy (unchanged price)
class NormalStrategy : IBillingStrategy
{
public double GetActPrice(double rawPrice) => rawPrice;
}
// Strategy for Happy hour (50% discount)
class HappyHourStrategy : IBillingStrategy
{
public double GetActPrice(double rawPrice) => rawPrice * 0.5;
}
Java
O exemplo a seguir está em Java .
import java.util.ArrayList;
import java.util.List;
interface BillingStrategy {
// Use a price in cents to avoid floating point round-off error
int getActPrice(int rawPrice);
// Normal billing strategy (unchanged price)
static BillingStrategy normalStrategy() {
return rawPrice -> rawPrice;
}
// Strategy for Happy hour (50% discount)
static BillingStrategy happyHourStrategy() {
return rawPrice -> rawPrice / 2;
}
}
class CustomerBill {
private final List<Integer> drinks = new ArrayList<>();
private BillingStrategy strategy;
public CustomerBill(BillingStrategy strategy) {
this.strategy = strategy;
}
public void add(int price, int quantity) {
this.drinks.add(this.strategy.getActPrice(price*quantity));
}
// Payment of bill
public void print() {
int sum = this.drinks.stream().mapToInt(v -> v).sum();
System.out.println("Total due: " + sum);
this.drinks.clear();
}
// Set Strategy
public void setStrategy(BillingStrategy strategy) {
this.strategy = strategy;
}
}
public class StrategyPattern {
public static void main(String[] arguments) {
// Prepare strategies
BillingStrategy normalStrategy = BillingStrategy.normalStrategy();
BillingStrategy happyHourStrategy = BillingStrategy.happyHourStrategy();
CustomerBill firstCustomer = new CustomerBill(normalStrategy);
// Normal billing
firstCustomer.add(100, 1);
// Start Happy Hour
firstCustomer.setStrategy(happyHourStrategy);
firstCustomer.add(100, 2);
// New Customer
CustomerBill secondCustomer = new CustomerBill(happyHourStrategy);
secondCustomer.add(80, 1);
// The Customer pays
firstCustomer.print();
// End Happy Hour
secondCustomer.setStrategy(normalStrategy);
secondCustomer.add(130, 2);
secondCustomer.add(250, 1);
secondCustomer.print();
}
}
Estratégia e princípio aberto / fechado
De acordo com o padrão de estratégia, os comportamentos de uma classe não devem ser herdados. Em vez disso, eles devem ser encapsulados por meio de interfaces. Isso é compatível com o princípio aberto / fechado (OCP), que propõe que as classes devem ser abertas para extensão, mas fechadas para modificação.
Como exemplo, considere uma classe de carro. Duas funcionalidades possíveis para o carro são freio e aceleração . Como os comportamentos de aceleração e freio mudam com frequência entre os modelos, uma abordagem comum é implementar esses comportamentos em subclasses. Essa abordagem tem desvantagens significativas: os comportamentos de aceleração e frenagem devem ser declarados em cada novo modelo de carro. O trabalho de gerenciamento desses comportamentos aumenta muito à medida que o número de modelos aumenta e exige que o código seja duplicado entre os modelos. Além disso, não é fácil determinar a natureza exata do comportamento de cada modelo sem investigar o código de cada um.
O padrão de estratégia usa composição em vez de herança . No padrão de estratégia, os comportamentos são definidos como interfaces separadas e classes específicas que implementam essas interfaces. Isso permite uma melhor dissociação entre o comportamento e a classe que usa o comportamento. O comportamento pode ser alterado sem quebrar as classes que o usam, e as classes podem alternar entre os comportamentos, alterando a implementação específica usada sem exigir nenhuma alteração significativa no código. Os comportamentos também podem ser alterados em tempo de execução, bem como em tempo de design. Por exemplo, o comportamento do freio de um objeto de carro pode ser alterado de BrakeWithABS()
para Brake()
, alterando o brakeBehavior
membro para:
brakeBehavior = new Brake();
/* Encapsulated family of Algorithms
* Interface and its implementations
*/
public interface IBrakeBehavior {
public void brake();
}
public class BrakeWithABS implements IBrakeBehavior {
public void brake() {
System.out.println("Brake with ABS applied");
}
}
public class Brake implements IBrakeBehavior {
public void brake() {
System.out.println("Simple Brake applied");
}
}
/* Client that can use the algorithms above interchangeably */
public abstract class Car {
private IBrakeBehavior brakeBehavior;
public Car(IBrakeBehavior brakeBehavior) {
this.brakeBehavior = brakeBehavior;
}
public void applyBrake() {
brakeBehavior.brake();
}
public void setBrakeBehavior(IBrakeBehavior brakeType) {
this.brakeBehavior = brakeType;
}
}
/* Client 1 uses one algorithm (Brake) in the constructor */
public class Sedan extends Car {
public Sedan() {
super(new Brake());
}
}
/* Client 2 uses another algorithm (BrakeWithABS) in the constructor */
public class SUV extends Car {
public SUV() {
super(new BrakeWithABS());
}
}
/* Using the Car example */
public class CarExample {
public static void main(final String[] arguments) {
Car sedanCar = new Sedan();
sedanCar.applyBrake(); // This will invoke class "Brake"
Car suvCar = new SUV();
suvCar.applyBrake(); // This will invoke class "BrakeWithABS"
// set brake behavior dynamically
suvCar.setBrakeBehavior( new Brake() );
suvCar.applyBrake(); // This will invoke class "Brake"
}
}
Veja também
- Injeção de dependência
- Função de ordem superior
- Lista de termos de programação orientada a objetos
- Mixin
- Design baseado em políticas
- Classe de tipo
- Entidade-componente-sistema
- Composição sobre herança
Referências
links externos
- Padrão de estratégia em UML (em espanhol)
- Geary, David (26 de abril de 2002). “Estratégia para o sucesso” . Padrões de projeto Java. JavaWorld . Página visitada em 2020-07-20 .
- Artigo de padrão de estratégia para C
- Refatoração: Substitua o Código de Tipo por Estado / Estratégia
- O padrão de design de estratégia na máquina de retorno (arquivado em 15-04-2017) Implementação do padrão de estratégia em JavaScript