Analysis Pattern: Regra de Lançamento (Posting Rule)

O que é

Comece simples ...

Introduz o acordo ...

Introduz Regras de Lançamentos ...

Fatorando situações duplicadas ...

Lidando com tempo ...

Quando deve ser usado

Código exemplo

class EventoContabil {
private TipoEvento tipo;
private Calendar quandoOcorreu;
private Calendar quandoObservado;
private Cliente cliente;
private Set lancamentosResultantes = new HashSet();
EventoContabil (TipoEvento tipo, Calendar quandoOcorreu,
Calendar quandoObservado, Cliente cliente) {
this.tipo = tipo;
this.quandoOcorreu = quandoOcorreu;
this.quandoObservado = quandoObservado;
this.cliente = cliente;
}
Cliente getCliente() {
return cliente;
}
TipoEvento getTipoEvento(){
return tipo;
}
Calendar getQuandoObservado(){
return quandoObservado;
}
Calendar getQuandoOcorreu(){
return quandoOcorreu;
}
void addLancamentoResultante(Lancamento lancamento){
lancamentosResultantes.add(lancamento);
}
RegraLancamento achaRegra() { /* discutido depois */}
void processa() { /* discutido depois */}
}
class TipoEvento implements ObjetoNomeavel {
public static TipoEvento CONSUMO = new TipoEvento("consumo");
public static TipoEvento CHAMADA = new TipoEvento("chamada serviço");
public TipoEvento(String nome){
super(nome);
}
}
class Lancamento {
private Calendar data;
private TipoLancamento tipo;
private Money valor;
public Lancamento (Money valor, Calendar data, TipoLancamento tipo) {
this.valor = valor;
this.data = data;
this.tipo = tipo;
}
public Money getValor() {
return valor;
}
public Calendar getData(){
return data;
}
public TipoLancamento getTipo(){
return tipo;
}
}
class TipoLancamento implements ObjetoNomeavel {
static TipoLancamento CONSUMO_BASICO = new TipoLancamento("Consumo Básico");
static TipoLancamento SERVICO = new TipoLancamento("Taxa de serviço");
public TipoLancamento(String nome){
super(nome);
}
}
class Cliente implements ObjetoNomeavel {
private AcordoServico acordoServico;
private List lancamentos = new ArrayList();
Cliente (String nome){
super(nome);
}
public void addLancamento (Lancamento lancamento){
lancamentos.add(lancamento);
}
public List getLancamentos(){
return Collections.unmodifiableList(lancamentos);
}
public AcordoServico getAcordoServico(){
return acordoServico;
}
public void setAcordoServico(AcordoServico acordo){
acordoServico = acordo;
}
}
class AcordoServico {
private double taxa;
private Map regrasLancamento = new HashMap();
void addRegraLancamento (TipoEvento tipoEvento, RegraLancamento regra, Calendar vigencia){
if (regrasLancamento.get(tipoEvento) == null) {
regrasLancamento.put(tipoEvento, new TemporalCollection());
}
temporalCollection(tipoEvento).put(vigencia, regra);
}
RegraLancamento getRegraLancamento(TipoEvento tipoEvento, Calendar quando){
return (RegraLancamento)temporalCollection(tipoEvento).get(quando);
}
private TemporalCollection temporalCollection(TipoEvento tipoEvento){
TemporalCollection resultado =(TemporalCollection)regrasLancamento.get(tipoEvento);
assert(resultado != null);
return resultado;
}
public double getTaxa(){
return taxa;
}
public void setTaxa(double novaTaxa){
taxa = novaTaxa;
}
}
abstract class RegraLancamento {
protected TipoLancamento tipo;
protected RegraLancamento (TipoLancamento tipo){
this.tipo = tipo;
}
private void facaLancamento(EventoContabil evento, Money valor){
Lancamento novoLancamento = new Lancamento (valor, evento.getQuandoObservado(), tipo);
evento.getCliente().addLancamento(novoLancamento);
evento.addLancamentoResultante(novoLancamento);
}
public void processa (EventoContabil evento){
facaLancamento(evento, calculaValor(evento));
}
abstract protected Money calculaValor(EventoContabil evento);
}

Como usar as classes para lidar com o consumo

public class Consumo extends EventoContabil {
private Quantidade valor;
public Consumo(Quantidade valor, Calendar quandoOcorreu, Calendar quandoObservado, Cliente cliente){
super(TipoEvento.CONSUMO, quandoOcorreu, quandoObservado, cliente);
this.valor = valor;
}
public Quantidade getValor(){
return valor;
}
double getTaxa(){
return getCliente().getAcordoServico().getTaxa();
}
}
class RegraMultiplicaPorTaxa extends RegraLancamento {
public RegraMultiplicaPorTaxa (TipoLancamento tipo){
super(tipo);
}
protected Money calculaValor(EventoContabil evento){
Consumo eventoDeConsumo =(Consumo)evento;
return Money.reais(eventoDeConsumo.getValor().getValor() * eventoDeConsumo.getTaxa());
}
}
class EventoContabil {
public void processa(){
achaRegra().processa(this);
}
RegraLancamento achaRegra(){
RegraLancamento regra =
cliente.getAcordoServico().getRegraLancamento( this.getTipoEvento(), this.quandoOcorreu);
assert(regra != null);
return regra;
}
...

public void configuraClienteNormal (){
cam = new Cliente("Cafe A Margot");
AcordoServico padrao = new AcordoServico();
padrao.setTaxa(10);
padrao.addRegraLancamento(
TipoEvento.CONSUMO,
new RegraMultiplicaPorTaxa(TipoLancamento.CONSUMO_BASICO),
criaCalendar(2003,1,1));
cam.setAcordoServico(padrao);
}
...
public void testConsumo(){
Consumo evento = new Consumo(
Unit.KWH.valor(50),
criaCalendar(2003,10,25),
criaCalendar(2003,10,25),
cam);
evento.processa();
Lancamento lancamentoResultante = getLancamento(cam, 0);
assertEquals (Money.reais(500), lancamentoResultante.getValor());
}

Um segundo tipo de evento

class EventoMonetario extends EventoContabil {
Money valor;
EventoMonetario(Money valor, TipoEvento tipo, Calendar quandoOcorreu,
Calendar quandoObservado, Cliente cliente) {
super(tipo, quandoOcorreu, quandoObservado, cliente);
this.valor = valor;
}
public Money getValor(){
return valor;
}
}
public void testServico(){
EventoContabil evento = new EventoMonetario(
Money.reais(40),
TipoEvento.CHAMADA,
criaCalendar(2003,10,25),
criaCalendar(2003,10,25),
cam);
evento.processa();
Lancamento lancamentoResultante =(Lancamento)cam.getLancamentos().get(0);
assertEquals (Money.reais(30), lancamentoResultante.getValor());
}
class RegraFormulaSimples extends RegraLancamento {
private double multiplicador;
private Money valorFixo;
RegraFormulaSimples (double multiplicador, Money valorFixo, TipoLancamento tipo){
super (tipo);
this.multiplicador = multiplicador;
this.valorFixo = valorFixo;
}
protected Money calculaValor(EventoContabil evento){
Money valorDoEvento =((EventoMonetario)evento).getValor();
return (Money)valorDoEvento.multiply(multiplicador).add(valorFixo);
}
}
public void configuraClienteNormal (){
cam = new Cliente("Cafe A Margot");
AcordoServico padrao = new AcordoServico();
padrao.setTaxa(10);
padrao.addRegraLancamento(
TipoEvento.CONSUMO,
new RegraMultiplicaPorTaxa(TipoLancamento.CONSUMO_BASICO),
criaCalendar(2003,1,1));
padrao.addRegraLancamento(
TipoEvento.CHAMADA,
new RegraFormulaSimples(0.5, Money.reais(10), TipoLancamento.SERVICO), criaCalendar(2003,1,1));

cam.setAcordoServico(padrao);
}
...

Lidando com uma mudança de regra

public void configuraClienteNormal (){
cam = new Cliente("Cafe A Margot");
AcordoServico padrao = new AcordoServico();
padrao.setTaxa(10);
padrao.addRegraLancamento(
TipoEvento.CONSUMO,
new RegraMultiplicaPorTaxa(TipoLancamento.CONSUMO_BASICO),
criaCalendar(2003,1,1));
padrao.addRegraLancamento(
TipoEvento.CHAMADA,
new RegraFormulaSimples(0.5,
Money.reais(10),
TipoLancamento.SERVICO),
criaCalendar(2003,1,1));
padrao.addRegraLancamento(
TipoEvento.CHAMADA,
new RegraFormulaSimples(0.5,
Money.reais(15),
TipoLancamento.SERVICO),
criaCalendar(2003,10,1));

cam.setAcordoServico(padrao);
}
...

public void testServicoDepoisDaMudanca(){
EventoContabil evento = new EventoMonetario(
Money.reais(40),
TipoEvento.CHAMADA,
criaCalendar(2003,10,5),
criaCalendar(2003,10,15),
cam);
Evento.processa();
Lancamento lancamentoResultante = (Lancamento)cam.getLancamentos().get(0);
assertEquals (Money.reais(35), lancamentoResultante.getValor());
}

Um segundo acordo ...

class RegraBaixaRenda extends RegraLancamento {
double taxa;
Quantidade limiteDeConsumo;
RegraBaixaRenda (TipoLancamento tipo, double taxa, Quantidade limiteDeConsumo) { super(tipo);
this.taxa = taxa;
this.limiteDeConsumo = limiteDeConsumo;
}
protected Money calculaValor(EventoContabil evento){
Consumo eventoDeConsumo = (Consumo)evento;
Quantidade consumoAtual = eventoDeConsumo.getValor();
return consumoAtual.isGreaterThan(limiteDeConsumo) ?
Money.reais(consumoAtual.getValor() * eventoDeConsumo.getTaxa()) :
Money.reais(consumoAtual.getValor() * this.taxa);
}
}
 private void configuraClienteBaixaRenda() {
zé = new Cliente("José Severino da Silva");
AcordoServico baixaRenda = new AcordoServico();
baixaRenda.setTaxa(10);
baixaRenda.addRegraLancamento(
TipoEvento.CONSUMO,
new RegraBaixaRenda(
TipoLancamento.CONSUMO_BASICO,
5,
new Quantidade(50, Unit.KWH)),
criaCalendar(2003, 1, 1));
baixaRenda.addRegraLancamento(
TipoEvento.CHAMADA,
new RegraFormulaSimples( 0, Money.reais(10), TipoLancamento.SERVICO), criaCalendar(2003, 10, 1));
zé.setAcordoServico(baixaRenda);
}
public void testConsumoBaixaRenda(){
Consumo evento = new Consumo(
Unit.KWH.valor(50),
criaCalendar(2003,10,1),
criaCalendar(2003,10,1),
zé);
evento.processa();
Consumo evento2 = new Consumo(
Unit.KWH.valor(51),
criaCalendar(2003,10,2),
criaCalendar(2003,10,2),
zé);
evento2.processa();
Lancamento lancamentoResultante1 = (Lancamento)zé.getLancamentos().get(0); assertEquals (Money.reais(250), lancamentoResultante1.getvalor());
Lancamento lancamentoResultante2 = (Lancamento)zé.getLancamentos().get(1); assertEquals (Money.reais(510), lancamentoResultante2.getvalor());
}

programa