Submarino.com.br

MoneyBag

Objetivo

Permitir manipular quantidades monetárias em diversas moedas sem recorrer a conversão.

Propósito

O padrão Money Bag (Bolsa de dinheiro) é uma extensão do padrão Money (Dinheiro). O padrão Money é principalmente útil quando trabalhamos com uma única moeda ou multiplas moedas mas elas não podem interagir directamente. Por exemplo, não podemos somar euros com dollars. Contudo em certas circunstâncias é mais simples permitir que haja esse agrupamento porque não queremos converter para uma moeda única.

A aplicação trabalha intensamente com valores monetários e é necessário operar sobre valores expressos em moedas diferentes. Usar o padrão Money ajuda, mas obriga a converter tudo para uma certa moeda base antes de prosseguir. Isto implica em obter uma taxa de conversão e perder os valores originais em cada moeda. No caso em que não podemos, ou não queremos, fazer a conversão, mas mesmo assim queremos ter apenas um objeto contento todo o valor monetário, temos um problema.

A solução passa por usar um objeto do mesmo tipo que Money que possa comportar vários valores em moedas diferentes. Para usar Money Bag é necessário que se use o padrão Money para começar já que todos os Money Bag são também Money.

Criamos uma classe do tipo MoneyBag e fazemos uma implementação equivalente à de Money. A diferença é que nas operações aritméticas deixaremos que a operação de soma e subtração aconteça mesmo quandos os objetos não têm a mesma moeda.Teremos, então, duas classes: Money que guarda apenas um valor e uma moeda e MoneyBag que guarda vários valores e várias moedas.

MoneyBag usará, internamente, objetos implementado o padrão Money para manter referencia aos valores em cada moeda.

Implementação

A implementação é relativamente simples. Fazemos MoneyBag herdar Money e alteramos as operações aritméticas de soma e subtração. Teremos que adicionar um método simplify() para que possamos reduzir um MoneyBag a um Money quando isso for possivel, ou seja, quando MoneyBag contiver o valor de apenas uma moeda.

A implementação a seguir de Money mostra apenas as alterações que seriam necessárias à classe Money exemplificada no padrão Money. Note-se que escolhemos modificar a forma como as operações são feitas de forma que haja integração entre Money e MoneyBag.

public class Money {

	protected Money (){
	
	}
	
	public Money plus ( Money other ){
		// usamos o mecanismo de moneyBag porque é generico. Isso evita usar ifs
		return new MoneyBag ().plus( this ).plus ( other ).simplify () ;
	}

	public Money subtract ( Money other ){
		return new MoneyBag () .plus ( this ) .subtract ( other ) .simplify () ;
	}

	public Money negate (){
		return new Money ( -amount, currency ) ;
	}
	
		// nivel de pacote
	Money getAmount ( Currency currency ){
		if ( currency.equals ( this.currency )){
			return this;
		}
	 	return new Money(0, currency) ;
	}

	public Money simplify (){
		return this ;
	}
}
Código 1: Classe Money
public class MoneyBag extends Money {

	private Map< Currency, Money > bag = new HashMap< Currency, Money >();
	
	private MoneyBag copy(MoneyBag other){
		MoneyBag m = new MoneyBag();
		m.bag.putAll(other.bag);
		return m; 
	}
	
	public Money plus ( Money other ){
		Money bagAmount = this.getAmount ( other.getCurrency ()) ;
		Money otherAmount = other.getAmount ( other.getCurrency ()) ;
		Money total = bagAmount.plus(otherAmount);
		
		MoneyBag result = MoneyBag.copy(this);
		result.bag.put ( other.getCurrency () ,  total ) ;
		
	}

	public Money subtract ( Money other ){
		return this.plus ( other.negate ()) ;
	}

	Money getAmount ( Currency c ){
		Money amount = bag.get ( other.getCurrency ()) ;
		if ( amount == null ){
			return new Money(0, c) ;
		}
		
		return amount;
	}
	
	public Currency getCurrency(){
		if ( bag.size () > 1 ){
			return new MultipleCurrency(bag.keySet());
		} else {
			return bag.keySet().iterator().next();
		}
	}

	public Money simplify (){
		if ( bag.size () == 1 ){
			Map.Entry entry = ( Map.Entry ) bag.entrySet () .iterator.next () ;
			return new Money ( entry.getValue () , entry.getKey ()) ;
		} else {
			return this ;
		}
	}
}
Código 2: Classe MoneyBag

Esta é uma das muitas implementações possiveis. Escolhemos fazer moneyBag um objeto imutável o que significa que a cada alteração temos que criar um objeto novo. Assim mantemos a coerencia com a imutabilidade da implementação padrão de Money.O ponto importante aqui é que MoneyBar contenha um conjunto de pares moeda-valor.

O objeto MoneyBag é na realidade um objeto Money também. Isto implica que MoneyBag tem uma moeda associada. Mas na realidade ele tem várias. Por isso, ele retorna uma instancia de MultipleCurrency sendo que só retorna uma única moeda quando apenas uma existe. Vejamos como seria a implementação desta classe:

public class MultipleCurrency extends Currency {

   private Set<Currency< currencies;
   private int hashCode;
   
   public MultipleCurrency(Set<Currency< currencies){
   		this.currencies = currencies;
   		this.hashCode = Hash.hash(currencies);
   }

   public boolean equals(Object other){
   	  return other instanceof MultipleCurrency && equalsMultipleCurrency((MultipleCurrency)other);
   } 
   
    public boolean equals(MultipleCurrency other){
   	  return CollectionUtils.isContentEqual(this.currencies, other.currencies);
   } 
   
   public int hashCode(){
   	 return hashCode;
   }
   
}
Código 3: Implementação de MultipleCurrency

Usamos os objetos auxiliares Hash que calcula o hash de uma coleção e CollectionUtils que verifica se os objetos dentro do Set são iguais independentemente da ordem. O hashCode é calculado apenas uma vez, já que a coleção de moedas não será modificada.

Repare que nunca uma MultipleCurrency será igual a uma outra Currency.

Conversões

Se estamos trabalhando com várias moedas provávelmente estamos trabalhando também com conversões. Se existir um serviço de conversão disponivel podemos implementar um método reduce() que força que as conversões aconteçam e seja retornado um único valor numa única moeda.

Este método pode ser usado no final dos calculos ou quando não podemos continuar os calculos sem fazer a conversão.

public Money reduce ( Date date, ConvertionService converter, Currency currency ){

	Money res = Money.valueOf ( "0.00", currency ) ;
	
	for ( Map.Entry entry : bag ){
		Money money = entry.getValue ()) ;
		res = res.plus ( converter.convert ( date, money, currency )) ;
	}
	return res;

}
				
Código 4: Método reduce

Discussão

Em termos práticos fazer MoneyBag herdar directamente de Money pode ser um problema. Algumas operações de Money como a distribuição são complexa se implementadas com MoneyBag e realmente não fazem muito sentido para este tipo de objeto.

Precisamos manter uma certa compabilidade com Money sem forçar que MoneyBag implemente todos os seus métodos.O padrão Composite Object pode ajudar.

Usado Composite Object podemos modelar uma interface comum aos dois tipos de objeto e construir implementações independentes mando comuns apenas as operações comuns.

public interface MoneyValue {

	public MoneyValue plus ( MoneyValue other ) ;
	public MoneyValue subtract ( MoneyValue other ) ;
	public MoneyValue negate () ;
	public MoneyValue simplify () ;
	public Money reduce ( Date date, ConvertionService converter, Currency currency ) ;
}
			
Código 5:

No método reduce() o retorno é concerteza de um só tipo de moeda. Por isso o retorno é garantidamente do tipo Money.

Uma outra opção menos complexa é não estabelecer nenhuma relação entre as classes MoneyBag e Money deixando o programador escolher entre usar um ou outro.

Padrões associados

O padrão MoneyBag está associado ao padrão Money e ao padrão Composite Object pois ele é, basicamente, a composição de objetos Money de moedas diferentes.