Strategy

Vantagens

Desvantagens

Exemplos

Exemplo #1

Uma revista deseja automatizar seu serviço de assinaturas e para isso precisa gerenciar a situação dos assinantes que realizam pagamentos por boleto. Uma assinatura pode estar “ativa” quando o pagamento está regularizado; “em atraso” durante os cinco dias corridos após o vencimento do boleto de pagamento; “em recuperação” entre 5 e 10 dias corridos após o vencimento do boleto, quando é feita a tentativa de regularização do pagamento junto ao assinante; “inadimplente” quando não há pagamento mesmo após 10 dias do vencimento e “suspensa” quando a interrupção da assinatura é solicitada pelo cliente. Exceto quando a assinatura está suspensa, sempre que o pagamento é realizado a situação dela muda para o estado “ativa”. Quando o pagamento ocorre após o vencimento, são cobrados juros de 10% sobre o valor base da assinatura. Caso esse pagamento seja feito durante a recuperação, os juros podem ser menores em função da negociação com o assinante. Em caso de inadimplência, são cobrados juros de 10% mais mora de 0,2 ao dia sobre o valor base e no caso de interrupção da assinatura, um valor proporcional pode ser cobrado. O conteúdo continuará sendo disponibilizado ao assinante enquanto a assinatura estiver ativa. Uma interface Payment define a operação calculateSubscriptionValue, que recebe como parâmetro o valor base da assinatura e retorna, dependendo da situação da assinatura, o valor que deve ser pago. As classes InterestChargesPayment, ProportionalPayment e DailyInterestPayment implementam Payment e sua operação e representam as possíveis formas de definir o valor a pagar da assinatura. A classe Subscription armazena o valor da assinatura e a data de vencimento estipulada, além de possuir as operações sendContent, que verifica o conteúdo disponibilizado, suspendSubscription quando é necessário que a assinatura seja interrompida e verifyPayment que verifica como está a situação do pagamento.

Diagrama de Classe

Exemplo #1 - Diagrama

Participantes

  • Strategy (Payment): Define uma interface comum para todas as operações de um estado, sendo essa interface usada por Context para chamar as operações do estado atual.
  • Concrete Strategy (InterestChargesPayment, ProportionalPayment, DailyInterestPayment): Implementa as operações definidas em Strategy para cada estado possível de Context.
  • Context (Subscription): Mantém uma referência para um objeto Concrete Strategy que representa seu estado atual e acessa suas operações através da interface Strategy.

Código

package strategy;

public class InterestChargesPaymentPolicy implements PaymentPolicy {

    // Método de cálculo do valor onde é definido um valor base de juros

    public float calculateSubscriptionValue(Subscription context){
        return context.getPlanValue() + context.getInterest()* context.getPlanValue();
    }
}
package strategy;

import java.util.Date;

public class Client {
    public static void main(String[] args) {

        // Payment Policies setup
        Subscription.policiesSetup(
                new DailyInterestPaymentPolicy(),
                new InterestChargesPaymentPolicy(),
                new ProportionalPaymentPolicy()
        );

        // Cria uma nova assinatura com a data de vencimento no dia atual do mês.
        Subscription subscription = new Subscription(29.00f, new Date(2020, 7, 6));

        System.out.println("\nAfter subscription start:");
        System.out.println("Total to pay: " + subscription.calculateSubscriptionValue());
        System.out.println("Receiving content? " + subscription.sendContent());

        System.out.println("\nDay after due date:");
        subscription.setInterestPaymentPolicy(0.1f);
        System.out.println("Total to pay: " + subscription.calculateSubscriptionValue());
        System.out.println("Receiving content? " + subscription.sendContent());

        System.out.println("\n6 days after due date:");
        subscription.setInterestPaymentPolicy(0.05f);
        System.out.println("Total to pay: " + subscription.calculateSubscriptionValue());
        System.out.println("Receiving content? " + subscription.sendContent());

        System.out.println("\n11 days after due date:");
        subscription.setDailyInterestPaymentPolicy(new Date(2020, 7, 17)); // colocar data aqui
        System.out.println("Total to pay: " + subscription.calculateSubscriptionValue());
        System.out.println("Receiving content? " + subscription.sendContent());

        System.out.println("\nAfter suspending subscription:");
        subscription.suspendSubscription(new Date(2020, 7, 25));
        System.out.println("Total to pay: " + subscription.calculateSubscriptionValue());
        System.out.println("Receiving content? " + subscription.sendContent());

        // Após a realização do pagamento é definido o novo prazo
        subscription.setDueDay(new Date(2020, 8, 6));
        subscription.activateSubscription();
    }
}
package strategy;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class ProportionalPaymentPolicy implements PaymentPolicy {

    // Pagamento proporcional à quantidade de dias entre a suspensão da assinatura
    // e a data de vencimento.
    public float calculateSubscriptionValue(Subscription context){
        // Cálculo da quantidade de dias
        long totalDays = context.getSuspensionDate().getTime() - context.getDueDay().getTime();
        totalDays = TimeUnit.DAYS.convert(totalDays, TimeUnit.MILLISECONDS);

        return context.getPlanValue() + 0.1f* context.getPlanValue() *totalDays;
    }
}
package strategy;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class DailyInterestPaymentPolicy implements PaymentPolicy {

    // Método de cálculo do valor onde é aplicada multa por dia de atraso

    public float calculateSubscriptionValue(Subscription context){
        // Cálculo da quantidade de dias de atraso
        long totalDays = context.getToday().getTime() - context.getDueDay().getTime();
        totalDays = TimeUnit.DAYS.convert(totalDays, TimeUnit.MILLISECONDS);

        return context.getPlanValue() + 0.1f* context.getPlanValue() + 0.2f*totalDays;
    }
}
package strategy;

public interface PaymentPolicy {
    // Interface que define a operação de cálculo do valor

    float calculateSubscriptionValue(Subscription context);

}
package strategy;

import java.util.Date;

public class Subscription {

    private final float planValue;

    private Date dueDay;

    private float interest;
    private Date suspensionDate;
    private Date today;

    private static PaymentPolicy dailyInterestPaymentPolicy;
    private static PaymentPolicy interestChargesPaymentPolicy;
    private static PaymentPolicy proportionalPaymentPolicy;

    // No construtor, são definidos o valor da assinatura e a data de vencimento do pagamento.
    public Subscription(float planValue, Date dueDay){
        this.dueDay = dueDay;
        this.planValue = planValue;

        this.interest = 0;
        this.suspensionDate = null;
        this.today = null;
    }

    public static void policiesSetup(
            PaymentPolicy _dailyInterestPaymentPolicy,
            PaymentPolicy _interestChargesPaymentPolicy,
            PaymentPolicy _proportionalPaymentPolicy
    ) {
        dailyInterestPaymentPolicy = _dailyInterestPaymentPolicy;
        interestChargesPaymentPolicy = _interestChargesPaymentPolicy;
        proportionalPaymentPolicy = _proportionalPaymentPolicy;
    }

    public boolean sendContent(){
        return this.verifyPayment() == null;
    }

    // Modificadores de situação
    public void activateSubscription() {
        this.interest = 0;
        this.suspensionDate = null;
        this.today = null;
    }

    public void setInterestPaymentPolicy(float interest) {
        this.interest = interest;
        this.suspensionDate = null;
        this.today = null;
    }

    public void setDailyInterestPaymentPolicy(Date today){
        this.today = today;
        this.suspensionDate = null;
        this.interest = 0;
    }

    public void suspendSubscription(Date today){
        this.suspensionDate = today;
        this.today = null;
        this.interest = 0;
    }


    private PaymentPolicy verifyPayment(){
        if(this.interest != 0)
            return interestChargesPaymentPolicy;
        if(this.suspensionDate != null)
            return proportionalPaymentPolicy;
        if(this.today != null)
            return dailyInterestPaymentPolicy;
        return null;
    }

    public float calculateSubscriptionValue() {
        PaymentPolicy paymentPolicy = this.verifyPayment();
        if(paymentPolicy == null)
            return this.planValue;
        return paymentPolicy.calculateSubscriptionValue(this);
    }


    // Getters & Setters

    public float getPlanValue() {
        return planValue;
    }

    public float getInterest() {
        return interest;
    }

    public Date getSuspensionDate() {
        return suspensionDate;
    }

    public void setSuspensionDate(Date suspensionDate) {
        this.suspensionDate = suspensionDate;
    }

    public Date getDueDay() {
        return dueDay;
    }

    public void setDueDay(Date dueDay) {
        this.dueDay = dueDay;
    }

    public Date getToday() {
        return today;
    }

    public void setToday(Date today) {
        this.today = today;
    }

}
Clique aqui para fazer o download do código completo de implementação deste Design Pattern.

Exemplo #2

O processo de fabricação de carros exige a execução de etapas como produção da carroceria, pintura e montagem dos equipamentos, que resultam em um carro pronto para ser dirigido. Para certos tipos de carro,  algumas dessas etapas exigem implementações específicas: em modelos Hatch, a carroceria é dividia em dois volumes: um para o motor e um para os passageiros e porta-malas, enquanto em modelos Sedan a carroceria é dividida em três volumes: um para o motor, um para os passageiros e um para o porta-malas. Isso significa que uma mesma implementação é irreplicável para ambos os casos. Uma interface Assembler possui as assinaturas dos métodos que realizam a construção de um carro (buildCarBodyWork, paintCar e mountCarParts) enquanto as classes SedanCar e HatchCar implementam Assembler e definem as implementações das operações. Quando é necessário construir, por exemplo, um carro Sedan, a classe Car mantém uma referência para uma instância de SedanCar e dentro do método manufacture executa os métodos de construção.
 

Diagrama de Classe

Exemplo #2 - Diagrama

Participantes

  • Strategy (Assembler) Define uma interface comum para os possíveis algoritmos, sendo usado por Context para chamar o algoritmo definido em uma instância concreta.

  • ConcreteStrategy (SedanCar, HatchCar) Implementa os algoritmos definidos pela interface Strategy.

  • Context (Car) Mantém uma referência para um objeto Strategy.

Código

package strategy;

public interface Assembler {
    //Definição das operações abstratas executáveis pelo algoritmo
    public abstract void buildCarBodyWork();
    public  abstract void paintCar();
    public abstract void mountCarParts();
}
package strategy;

public class Car {

    private Assembler carAssembler;

    //Método que executa as etapas de Strategy
    public void manufacture(){
        carAssembler.buildCarBodyWork();
        carAssembler.paintCar();
        carAssembler.mountCarParts();
    }

    public void setCarAssembler(Assembler carAssembler) {
        this.carAssembler = carAssembler;
    }
}
package strategy;

public class Client {
    public static void main(String[] args) {

        Car car = new Car();

        //Instancia uma classe para a execução de um algoritmo SedanCar e passa essa instância para a instância de Car.
        SedanCar sedanCar = new SedanCar();
        car.setCarAssembler(sedanCar);

        car.manufacture();
    
        System.out.println("Car info:");
        System.out.println(sedanCar.getCarInfo());
    }
}
package strategy;

public class HatchCar implements Assembler {

    // Atributos de Car que serão modificados durante as etapas do algoritmo.
    private String color;
    private int bodyWorkVolumesNumber;
    private String engineType;
    private String tireType;
    private String starterMotorType;

    // Implementações específicas para cada etapa do algoritmo que constrói um carro Hatch
    public void buildCarBodyWork(){
        this.bodyWorkVolumesNumber = 2;
    }

    public void paintCar() {
        this.color = "blue";
    }

    public void mountCarParts(){
        this.engineType = "V type";
        this.tireType = "All Season";
        this.starterMotorType = "Direct Drive";
    }

    public String getCarInfo(){
        return "Number of bodywork volumes: " + this.bodyWorkVolumesNumber
             + "\nCar color: " + this.color
             + "\nEngine type: " + this.engineType
             + "\nTire type: " + this.tireType
             + "\nStarter motor type: " + this.starterMotorType;
    }
}
package strategy;

public class SedanCar implements Assembler {

    // Atributos de Car que serão modificados durante as etapas do algoritmo.
    private String color;
    private int bodyWorkVolumesNumber;
    private String engineType;
    private String tireType;
    private String starterMotorType;

    // Implementações específicas para cada etapa do algoritmo que constrói um carro Hatch
    public void buildCarBodyWork(){
        this.bodyWorkVolumesNumber = 3;
    }

    public void paintCar() {
        this.color = "black";
    }

    public void mountCarParts(){
        this.engineType = "W type";
        this.tireType = "Summer";
        this.starterMotorType = "Planetary Gear";
    }

    public String getCarInfo(){
        return "Number of bodywork volumes: " + this.bodyWorkVolumesNumber
             + "\nCar color: " + this.color
             + "\nEngine type: " + this.engineType
             + "\nTire type: " + this.tireType
             + "\nStarter motor type: " + this.starterMotorType;
    }

}
Clique aqui para fazer o download do código completo de implementação deste Design Pattern.

Padrões Relacionados

Este Padrão pode ser usado para resolver os seguintes problemas: