Permite um objeto alterar o seu comportamento quando o seu estado interno muda. O objeto parecerá ter mudado sua classe.
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; “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%. 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 e, no caso de interrupção da assinatura, um valor proporcional pode ser cobrado. Uma classe abstrata Situation representa um estado de uma assinatura e as classes Active, Late, Recovering, Overdue e Suspended herdam de Situation e definem os estados possíveis e seus comportamentos. Situation também apresenta a operação calculateSubscriptionValue, que recebe a assinatura como parâmetro e retorna o valor total que deve ser pago, dependendo da situação. 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.
package state;
public class Active implements Situation {
// Cálculo para situação "Ativa". Apenas retorna o valor base da assinatura.
public float calculateSubscriptionValue(Subscription context){
return context.getPlanValue();
}
}
package state;
import java.util.Date;
public class Client {
public static void main(String[] args) {
// Subscription policies setup
Subscription.statesSetup(
new Active(),
new Late(),
new Recovering(),
new Overdue(),
new Suspended()
);
// 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.setSubscriptionLate();
System.out.println("Total to pay: " + subscription.calculateSubscriptionValue());
System.out.println("Receiving content? " + subscription.sendContent());
System.out.println("\n6 days after due date:");
subscription.setSubscriptionRecovering(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.setSubscriptionOverdue(new Date(2020, 7, 17)); // Data de hoje
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.setDueDate(new Date(2020, 8, 6));
subscription.activateSubscription();
}
}
package state;
public class Late implements Situation {
// Na situacao "Atrasada", sao cobrados juros de 10% sobre o valor da assinatura
public float calculateSubscriptionValue(Subscription context){
return context.getPlanValue() + 0.1f* context.getPlanValue();
}
}
package state;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Overdue implements Situation {
// Na situacao "Inadimplente", sao cobrados juros de 10% mais 0,2 de multa diarios.
public float calculateSubscriptionValue(Subscription context){
// Calculo dos dias de atraso para definir o valor da multa
long totalDays = context.getToday().getTime() - context.getDueDate().getTime();
totalDays = TimeUnit.DAYS.convert(totalDays, TimeUnit.MILLISECONDS);
return context.getPlanValue() + 0.1f* context.getPlanValue() + 0.2f*totalDays;
}
}
package state;
public class Recovering implements PaymentState {
public float calculateSubscriptionValue(Subscription context){
return context.getPlanValue() + context.getNegotiatedInterest()* context.getPlanValue();
}
}
package state;
public interface Situation {
// Interface que define a operacao de calculo do valor a ser pago
public abstract float calculateSubscriptionValue(Subscription context);
}
package state;
import java.util.Date;
public class Subscription {
private final float planValue;
private Date dueDate;
private boolean lateInterest;
private float negotiatedInterest;
private Date suspensionDate;
private Date today;
private static Situation active;
private static Situation late;
private static Situation recovering;
private static Situation overdue;
private static Situation suspended;
// No construtor, são definidos o valor da assinatura e a data de vencimento do pagamento.
public Subscription(float planValue, Date dueDay){
this.dueDate = dueDay;
this.planValue = planValue;
this.lateInterest = false;
this.negotiatedInterest = 0;
this.suspensionDate = null;
this.today = null;
}
public static void statesSetup(
Situation _active,
Situation _late,
Situation _recovering,
Situation _overdue,
Situation _suspended
) {
active = _active;
late = _late;
recovering = _recovering;
overdue = _overdue;
suspended = _suspended;
}
public boolean sendContent(){
return verifyPayment() == active;
}
// Modificadores de situação
public void activateSubscription() {
this.suspensionDate = null;
this.today = null;
this.lateInterest = false;
this.negotiatedInterest = 0;
}
public void setSubscriptionLate() {
this.suspensionDate = null;
this.today = null;
this.lateInterest = true;
this.negotiatedInterest = 0;
}
public void setSubscriptionRecovering(float negotiatedInterest){
this.suspensionDate = null;
this.today = null;
this.lateInterest = false;
this.negotiatedInterest = negotiatedInterest;
}
public void setSubscriptionOverdue(Date today) {
this.today = today;
this.suspensionDate = null;
this.lateInterest = false;
this.negotiatedInterest = 0;
}
public void suspendSubscription(Date today){
this.suspensionDate = today;
this.today = null;
this.lateInterest = false;
this.negotiatedInterest = 0;
}
private Situation verifyPayment(){
if(this.lateInterest)
return late;
if(this.negotiatedInterest != 0)
return recovering;
if(this.suspensionDate!=null)
return suspended;
if(this.today!=null)
return overdue;
return active;
}
public float calculateSubscriptionValue() {
Situation situationSubscription = this.verifyPayment();
return situationSubscription.calculateSubscriptionValue(this);
}
// Getters & Setters
public float getPlanValue() {
return planValue;
}
public float getNegotiatedInterest() {
return negotiatedInterest;
}
public Date getSuspensionDate() {
return suspensionDate;
}
public void setSuspensionDate(Date suspensionDate) {
this.suspensionDate = suspensionDate;
}
public Date getDueDate() {
return dueDate;
}
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
public Date getToday() {
return today;
}
public void setToday(Date today) {
this.today = today;
}
}
package state;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Suspended implements Situation {
// No estado "Suspenso", é necessário saber a data do vencimento e a data da suspensão
// da assinatura para calcular o valor a ser pago.
public float calculateSubscriptionValue(Subscription context){
// Cálculo de dias entre o vencimento e o cancelamento da assinatura
long totalDays = context.getSuspensionDate().getTime() - context.getDueDate().getTime();
totalDays = TimeUnit.DAYS.convert(totalDays, TimeUnit.MILLISECONDS);
return context.getPlanValue() + 0.1f* context.getPlanValue() *totalDays;
}
}