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 e é principalmente útil quando trabalhamos com uma única moeda ou múltiplas moedas mas elas não podem interagir diretamente. 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.

Quando a aplicação trabalha intensamente com valores monetários e é necessário operar simultaneamente 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. Já para não falar em problemas de arredondamento. 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 também o padrão Money 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 quando 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 simples, quando isso for possível, 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 é genérico. 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 ) ;
	}

	// nível de pacote
	Money getAmount ( Currency currency ){
		if ( currency.equals ( this.currency )){
			return this;
		}
	 	return new Money(0, currency) ;
	}

	public Money simplify (){
		return this ;
	}
}
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 ;
		}
	}
}

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 coerência com a imutabilidade da implementação padrão de Money .O ponto importante aqui é que MoneyBag 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;
   }

}

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 provavelmente estamos trabalhando também com conversões. Se existir um serviço de conversão disponível 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 cálculos ou quando não podemos continuar os cálculos 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;

}

Discussão

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

Precisamos manter uma certa compatibilidade 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 mantendo 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 ) ;
}

No método reduce() o retorno é com certeza 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.

Assim como Money, MoneyBag também está relacionado aos padrões Value Object e Quantity.

Scroll to Top