Money

Objectivo

Prover manipulação correta de quantidades monetárias.

Propósito

Na maioria das aplicações comerciais é necessário trabalhar com valores monetários, com dinheiro. As operações com dinheiro são um pouco diferentes das operações matemáticas habituais.

  1. não podemos somar dinheiro em moedas diferentes. Se o sistema usa moedas diferentes é fácil cometer este erro.

  2. não é possível dividir dinheiro além da uma certa menor unidade (normalmente o centavo). Ou seja, não ha como dividir 0.01 dólares por várias pessoas porque é como tentar dividir uma moeda de um cêntimo. Além disso as operações de divisão têm que ser cuidadosamente verificadas para que não se destrua (ou crie) dinheiro por causa de arredondamentos.

Do ponto de vista técnico ha muito tempo que é sabido que tipos numéricos como double ou float não são suficientes para elaborar algoritmos que lidam com dinheiro e, em Java, nos vemos obrigados a usar BigDecimal por ser o menos pior. Contudo a classe BigDecimal tem as suas peculiaridades que acabam atrapalhando mais do que ajuda; como o respeito a numero significativos e a não possibilidade de representar dizimas infinitas como um terço.

Ao trabalhar com dinheiro é necessário seguir algumas diretrizes:

  • Dinheiro de moedas diferentes não pode ser somado ou subtraído

  • Não é possível multiplicar quantidades de dinheiro por outras quantidades de dinheiro.

  • Existe uma quantidade miníma indivisível.

  • A quantidade mínima indivisível não é a mesma para todas as moedas

Isto deixa claro que para representar dinheiro corretamente ele tem que estar associado a uma moeda.

A proposta do padrão Money é, portanto, associar uma quantidade numérica a uma moeda de forma a prover operações aritméticas corretas sobre as quantidades monetárias.

Implementação

Representação

Na versão mais simples do padrão basta associar um valor e uma representação de moeda. Sendo que este objeto irá representar um valor ele deve seguir o padrão Value Object.

A representação da moeda pode variar conforme as necessidades da aplicação. Pode ser implicita se o sistema apenas utilizar uma moeda; e, neste caso, não existe realmente uma representação da moeda no objeto Money.

Para a representação do valor não podemos escolher double ou float já que todo o objetivo do padrão Money é resolver as limitações desses tipos numéricos. Estes tipos não conseguem representar potências negativas de 10 (como 0.1 ou 0.01), que são muito comuns quando se trabalha com dinheiro.

Um melhor opção seria utilizar valores inteiros. Isto simplificaria os cálculos pois todos eles serão com números inteiros, não sendo necessários arredondamentos. Mas para usar matemática de inteiros temos que reduzir o valor monetário decimal à sua unidade mais pequena e indivisível ( por exemplo, 3.14 dólares seria representado como 314 cêntimos).

O problema é que nem todas as moedas do mundo têm a mesma unidade indivisível. Isto obriga a escolher entra usar um tipo decimal especial como BigDecimal ou ter acesso à informação da menor unidade da moeda reformulando a nossa representação da moeda associada.

Felizmente Java é uma plataforma preparada para a internacionalização e localização de aplicações. Java conta com a classe Currency que identifica as moedas pelo padrão ISO 4217 e permite conhecer a menor unidade através do método getDefaultFractionDigits().

Portanto, uma implementação básica seria mais ou menos assim:

public final class Money {

    // métodos de criação
	public static Money valueOf(String value,String currencyIsoCode){
		return valueOf(new BigDecimal(value),currency)
	}

	public static Money valueOf(BigDecimal value,String currencyIsoCode){
		return valueOf(new BigDecimal(value),Currency.getInstance(currencyIsoCode))
	}

	public static Money valueOf(BigDecimal value,Currency currency){
		return new Money(toLongRepresentation(value,currency),currency);
	}

	// métodos para redução de, e para, long
	private static long toLongRepresentation(BigDecimal value,Currency currency){
		return value.movePointRight(currency.getDefaultFractionDigits()).longValue();
	}

	private static BigDecimal fromLongRepresentation(long amount,Currency currency){
		BigDecimal value = new BigDecimal(amount);
		return value.movePointLeft(currency.getDefaultFractionDigits());
	}

	// contrutor privado
	private Money (long value,Currency currency){
		this.value = value;
		this.currency = currency;
	}

	private long value;
	private Currency currency;

	public BigDecimal getAmount(){
		return fromLongRepresentation(value, currency);
	}

	public Currency getCurrency(){
		return currency;
	}

}

Operações

Com dinheiro apenas são possiveis operações de soma e subtração (desde que na mesma moeda), multiplicação por numeros decimais e divisão por inteiros. Notar que a divisão por numeros decimais é igual à multiplicação pelo inverso desse número.

As operações não precisam ser implementadas no objeto se ele não for utilizado para calculo e apenas como representação, mas são implementadas na maioria das vezes por serem uma facilidade prática.

Para as operações de soma e subtração é necessário testar se a moeda de ambas as parcelas é a mesma. Caso isso não seja verdade uma exceção será lançada.

public final class Money {
   // o resto dos métodos


   public Money plus(Money other){
   		if (!other.currency.equals(this.currency)){
   			throw new CurrencyMismatchException(this.currency,other.currency);
   		}

   		// é a mesma moeda. Money é imutável. Cria outro com novo valor
   		return new Money(this.value + other.value, this.currency);

   }

   public Money subtract(Money other){
   		if (!other.currency.equals(this.currency)){
   			throw new CurrencyMismatchException(this.currency,other.currency);
   		}

   		// é a mesma moeda. Money é imutável. Cria outro com novo valor
   		return new Money(this.value - other.value, this.currency);

   }
}

A operação de multiplicação é um pouco mais delicada. Porque é possivel multiplicar por uma quantidades decimal qualquer podemos criar um método que aceite qualquer numero,ou, que aceite apenas BigDecimal . Às vezes por compatibilidade com API legadas ou frameworks é necessário utilizar Double

public final class Money {
	// o resto dos métodos

	public Money multiply (Number factor){
		BigDecimal bigFactor;
		if (factor instanceof BigDecimal){
			bigFactor = (BigDecimal)factor;
		} else {
			bigFactor = new BigDecimal(factor.toString());
		}

		long result = bigFactor.multiply(new BigDecimal(this.value)).longValue();

		return new Money(result,currency);
	}
}

Apenas o valor é multiplicado e os decimais são desprezados.

A operação de divisão é mais complexa porque tem que seguir a regra de divisão inteira. Na realidade se trata de uma operação de distribuição. Felizmente a classe BigInteger já contém essas operações.

public final class Money {
	// o resto dos métodos

	public Money[] distribute (int n){

		BigInteger bigValue =  BigInteger.valueOf(this.value);
		BigInteger[] result = bigValue.divideAndRemainder(BigInteger.valueOf(n));

		Money[] distribution = new Money[n];

		// todos os valores são iguais
		Arrays.fill(distribution, result[0]);

		// adiciona o resto no primeiro
		// substituindo o valor atual nessa posição
		distribution[0] = distribution[0].plus(new Money(result[1],this.currency));

		return distribution;
	}
}

Este método produz n parcelas cuja soma é igual ao valor original. Isto é muito útil em sistemas de venda que permitem parcelamento. Note-se ainda que várias variações deste método são possíveis.Por exemplo, um em que a distribuição não é equitativa.

Discussão

Money é um padrão que pretende ser o melhor tipo de dados para trabalhar com dinheiro. É uma especialização do padrão Quantity que associa a um valor uma unidade, permitindo controlar operações inválidas como somar valores em moedas diferentes.

Em Java a principal característica da implementação do padrão é permitir que não se use BigDecimal ou double para guardar o valor monetário e fazer cálculos usando inteiros de forma que não ha problemas de arredondamento.

Mesmo em sistema que trabalham apenas com uma moeda o uso de Money é prudente. Primeiro porque fornece tipagem forte para um tipo especial (dinheiro) - seguindo a filosofia de Tiny Types. Segundo porque - se o sistema for bem desenhado - será mais fácil estender o sistema para trabalhar em mais moedas.

Suporte da API padrão

O padrão Money não é implementado na API padrão por ser um padrão de domínio especialmente usado em aplicações relacionadas à manipulação de dinheiro ou de entidades liquidáveis. Contudo uma JSR (354) foi criada para criar uma API padrão para Money.

Mesmo não usando a JSR 354, devido ao suporte dado pela classe Currency é possivel criar nossa própria implementação para nossos casos de uso. Isto não é uma tarefa tão simples como pode parecer porque devido à aritmética especial do dinheiro algumas contas não podem ser feitas.

Padrões associados

Money é um uso especifico do padrão Quantity que por sua vez é um uso especifico do padrão Value Object.

Em vez de usar inteiros ou BigDecimal o padrão Money pode ser implementado utilizando objeto Ratio para o valor, seguindo o padrão Ratio.

Um outro padrão relacionado a Money é MoneyBag que permite trabalhar com dinheiro em moedas diferentes sem implicar na conversão para uma moeda base. Ideal se ha operações com diferentes moedas simultaneamente.

Bibliografia

[1] Martin Fowler Patterns of Enterprise Application Architecture: Addison-Wesley Professional (2003)

Scroll to Top