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

Um exemplo de classe UML e diagrama de sequência para o padrão de design de Estratégia.

No diagrama de classes UML acima , a Contextclasse não implementa um algoritmo diretamente. Em vez disso, Contextrefere-se à Strategyinterface para executar um algoritmo ( strategy.algorithm()), que Contextindepende de como um algoritmo é implementado. As classes Strategy1e Strategy2implementam a Strategyinterface, ou seja, implementam (encapsulam) um algoritmo.
O diagrama de sequência UML mostra as interações em tempo de execução: O Contextobjeto delega um algoritmo para diferentes Strategyobjetos. Primeiro, Contextchama algorithm()um Strategy1objeto, que executa o algoritmo e retorna o resultado para Context. Depois disso, Contextmuda sua estratégia e chama algorithm()um Strategy2objeto, que executa o algoritmo e retorna o resultado para Context.

Diagrama de classes

Padrão de Estratégia em UML

Padrão de estratégia em LePUS3 ( legenda )

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

Os comportamentos de aceleração e frenagem devem ser declarados em cada novo modelo de carro .

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 brakeBehaviormembro 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

Referências

links externos