Money

Este texto foi atualizado e doado ao projeto Code Design World.
Leia a versão mais atualizada.

Money

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, mas a principal característica do padrão é permitir que não se use BigDecimal ou double para guardar o valor monetário.

O Problema

Na maioria das aplicações comerciais é necessário trabalhar com valores monetários: dinheiro. As operações com dinheiro são um pouco diferentes de simples operações matemáticas. Para começar não podemos somar dinheiro em moedas diferentes. Se o sistema usa moedas diferentes é fácil cometer este erro. Depois, dividir dinheiro além da menor unidade possivel( normalmente o centavo) não é possível. 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. Contudo a classe BigDecimal tem as suas peculiaridades que acabam atrapalhando mais do que ajudam porque embora forneçam mecanismos de calculo, não fornece um mecanismo de controle de unidades.

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

  • Quantidades de moedas diferentes não podem ser somadas
  • A multiplicação apenas acontece por quantidades adimencionais.
  • A divisão tem que ser inteira pois não pode haver resto menor que a minima unidade monetária( centavo, por exemplo)
  • A minima unidade monetária não é igual para todas as moedas.

A Solução

Trabalhar com dinheiro não se resume simplesmente trabalhar com um numero. É trabalhar com um numero e uma unidade – a Moeda. A solução proposta pelo padrão Money é simples: criar uma classe – Money – que force o conjunto de regras para as operações com dinheiro e possa ser usada de forma semelhante a um numero, como BigDecimal seria. O padrão Money é uma aplicação especifica do padrão Quantity que por sua vez é uma aplicação do padrão Value Object e por isso o objeto Money é imutável. Ou seja, não existem métodos que modificam o valor do objeto (setters).

Como funciona

O objecto Money representa um valor monetário associado a uma moeda e encapsula as operações sobre essa quantidade.

Implementação

O padrão Money nos ajuda a manipular mais facilmente o valor. Para isso a quantidade é guardada como um inteiro na minima unidade possivel. No caso da maioria das moedas seria centavos, mas na realidade o numero máximo de casas decimais do do valor monetário para uma moeda especifica é diferente do que para outra. Em java esse numero é dado pelo método getDefaultFractionDigits() da classe Currency.

01
02 public class Money {
03
04 Currency currency;
05
06 public boolean hasSameCurrency ( Money other ){}
07 public Money plus ( Money other ){ }
08 public Money minus ( Money other ){ }
09 public Money multiply ( Number scalar ){ }
10 public Money [] divide ( int other ){ }
11 }

Código 1:

Usando um inteiro para representar o valor as operação de soma e subtração são extremamente simples já que se limitam a somar e subtrair inteiros. No caso da multiplicação basta multiplicar a quantidade pelo numero dado.

No caso da divisão métodos especiais têm que ser fornecidos para acomodar as diferentes formas de dividir dinheiro. Divisão de dinheiro por numeros inteiros representa a distribuição do valor em parts iguais sendo que o minimo valor não pode ser repartido. Logo 1 não pode ser repartido, resultando sempre em zero e resto 1. Existe distribuição sem resto, onde o resto é adicionado a uma das quantidades resultantes (por exemplo 100 em 3 parcelas equivale a uma parcela de 33.34 e duas de 33.33

Como o objeto Money é imutável e pretendemos fazer uso extenso dele, é também util esconder o seu construtor e fornecer métodos utilitários para a sua criação. Finalmente, alguns métodos de conversão para BigDecimal são fornecidos. O objetivo desdes métodos se referem a poder converter o valor de Money , sem perder exatidão, para ser usado com API que só aceitam BigDecimal (como por exemplo JDBC)

01
02 public class Money {
03
04 Currency currency;
05 long amount;
06
07 public static Money money ( String value, Currency currency ){
08 return Money ( amount,currency ) ;
09 }
10
11 public static Money money ( String value, String currencyCode ){
12 return Money ( amount,Currency.getCurrency ( currencyCode )) ;
13 }
14
15
16 private Money ( String amount, Currency currency ){
17 this .currency = currency;
18 this .amount = Long.parseLong ( amount ) * ( long ) Math.pow ( 10 , currency.getDefaultFractionDigits ()) ;
19 }
20 public boolean hasSameCurrency ( Money other ){
21 return this .currency.equals ( other.currency ) ;
22 }
23
24 public Money plus ( Money other ){
25 if ( ! this .hasSameCurrency ( other )){
26 throw new IllegalArgumentException ( “Cannot sum money in different currencies” ) ;
27 }
28 return new Money ( this .amount + other.amount, this .currency ) ;
29 }
30 public Money minus ( Money other ){ }
31 public Money multiply ( Number scalar ){
32 return new Money ( ( long )( this .amount * scalar.doubleValue ()) , this .currency ) ;
33 }
34
35 /**
36 * Devisão sem resto, resulta num array de n posições onde o resto foi somado no
37 * item 0 do array.
38 */
39 public Money [] distribute ( int n ){ }
40
41
42 /**
43 * Devisão com resto, resulta num array de 2 posições onde o valor é retornado no
44 * item 0 e o resto no item 1
45 */
46 public Money [] distributeEqually ( int n ){ }
47
48 public BigDecimal asNumber (){
49 return new BigDecimal ( amount / Math.pow ( 10 , currency.getDefaultFractionDigits ())) ;
50 }
51
52 public String toString (){
53 return asNumber () .toString () ;
54 }
55 }

Código 2:

Outras implementações dos métodos seriam possiveis além de que mais funcionalidades poderiam ser adicionadas a Money como, por exemplo, implementar Comparable. Uma implementação do padrão Money é a forma correta de representar dinheiro em Java. É uma pena que ainda não exista uma implementação desse padrão na API standard.

Conversões

Quando um sistema manipula quantidades monetárias em mais do que uma moeda normalmente é necessário algum tipo de conversão. A conversão é básicamente uma multiplicação do valor em uma moeda por um factor. O problema é que esse fator se altera a cada dia. A conversão é tipicamente abstraida na forma de um serviço ( uma classe sem atributos, apenas métodos) Com o uso do padrão Money o contrato de dito serviço é simplificado e o codigo que processa a transformação tira partido de todas as regras contidas no objeto.

01
02
03 public class ConvertionService {
04
05 public Money convert ( Date date, Money money, Currency endCurrency ){
06 if ( money.getCurrency () .equals ( targetCurrency )){
07 return money;
08 }
09 final Double factor = getFactor ( date, money.getCurrency () ,endCurrency ) ;
10 return Money.money ( money.getAmount () .multiply ( factor ) , endCurrency ) ;
11
12 }
13
14 private Double getFactor ( Date date,Currency startCurrency , Currency endCurrency ){
15 //obtém factor conforme as moedas e a data
16 }
17
18 }

Código 3:

Dinheiro por extenso

Uma funcionalidades normalmente necessária ao trabalhar com dinheiro é a de poder escrever o valor por extenso. Isso é relativamente fácil se partirmos de um numero, mas mais facil ainda se partirmos de um objeto Money já que podemos até completar com o nome da moeda em causa. Por outro lado, a forma como é escrito o valor e os nomes das unidades da moeda( unidades principal e subunidades) dependem da moeda contida o objeto Money.

Padrões Relacionados

No padrão Money (Dinheiro) associamos um valor a uma moeda. Esta é um uso especializado do padrão Quantity (Quantidade) em que associamos um valor a uma unidade. O padrão Quantity e, por conseguinte, o padrão Money são ambos especilizações do padrão Value Object (Objecto de Valor).

Persistindo Money e Currency

A implementação do padrão Money tal como apresentado até aqui serviu para apresentar o conceito e as vantagens do padrão. Contudo, muitas das aplicação que manipulam dinheiro também necessitam de persistir informações. A persistência de um objeto Money não se resume a persistir o valor contido nele. Ha que tomar em atenção a moeda em que o valor é representado. Se o sistema usa apenas uma moeda o controle é simples. Basta associar sempre a mesma moeda ao valor lido do banco. Se o sistema usa mais do que uma moeda é necessário saber de onde ler a moeda que é associada a aquele valor. Normalmente a moeda é representa por um outro campo na mesma tabela ou associada a uma entidade ‘moeda’, mas cada caso é um caso.

Usámos a classe Currency por ser a classe padrão do java para representar moedas e porque nos fornece informação sobre o máximo numero de casas decimais. Contudo, num sistema real talvez queiramos usar uma entidade com mais propriedades. Não ha nenhum problema com isso. Embora Money seja um Value Object não ha nenhum problema em ele referir outras entidades. Ao implementar um novo tipo de classe Currency apenas é necessário tem em mente o correto mapeamento da moeda para o valor retornado por currency.getDefaultFractionDigits().

No caso mais simples de se usar apenas uma moeda em que a quantidade de casas decimais é conhecida a implementação pode ser simplificada incorporando essas regras conhecidas e não precisando de especificar a moeda.

Embora o padrão Money seja incentivado e muito útil durante a escrita de algoritmos que manipulam valores monetários a sua persistência não é trivial já que existe um mapeamento de um atributo para dois campos. Mesmo assim vale a pena usar este padrão sempre que seu sistema manipular dinheiro.

Licença

Creative Commons License Sérgio Taborda
Este trabalho é licenciado sob a
Licença Creative Commons Atribuição-Uso Não-Comercial-Não a obras derivadas 3.0 Genérica .

2 opiniões sobre “Money”

  1. Sérgio bom dia, muito interessante o artigo parabens.
    Estou com uma dúvida com relação a usabilidade deste padrão. Gostaria de saber se você tem conhecimento de grandes aplicações que executam diversas operações financeiras como: multiplicação e divisão de números com casas decimais diferentes, e a questão do arredondamento ou truncamento.
    obrigado

  2. Daquilo que conheço não ha uma regra padrão. As pessoas tendem a escolher o velho arredondamento para cima se maior ou igual a .5 e para baixo se menor que .5. Truncamento jamais. O mais importante em operações com dinheiro é manter a regra : dinheiro não de cria, nem se destrói, apenas se transfere. O dinheiro funciona como um conjunto da menor unidade possível para a moeda ( o centavo, usualmente) e não da moeda em si. Isto porque a menor transação possivel é aquele representada pelo menor papel-moeda ou metal-moeda possível. Ou seja, 100 reais /3 = 33,33 e nunca poderá ser 33,333 porque não existe um moeda ou nota de 0.001 reais. Daqui que o valor tenha que ser arredondado para parcelas diferentes (33,34 + 33,33 +33,33 ) ou 3 parcelas iguais de 33,33 e o centavo sobrante vai para uma conta especial de equidade. Em tese, estatisticamente essa conta zera se tivermos o cuidado de retirar centavos de lá ( o que normalmente não acontece). Por exemplo, 33,33 + 33,33 + 33,32 = 99,98 , então retirariamos um centavo dessa conta especial para obter três parcelas de 33,33.
    Cada banco, cada gerente, cada pessoa com que vc falar terá um forma de fazer as contas. A preocupação é nunca perder dinheiro e nunca utilizar unidades menores que o possível. Isso exige normalmente que as contas sejam feitas em um certa ordem para que haja o minimo de erros possiveis.
    Quando trabalhando taxas elas tên normalmente várias cassas ( de 4 a 6 digitos) mas o resultado final sempre será arredondado até ao centavo.
    Por exemplo: 100 / 0.01435 = 6968,64111985 ~= 6968,64

    uma possibilidade de arredondamento que se utiliza em ciencias porque é a que , estatisticamente, incorre em menos desvios é a que o Java chama de Half_Even. Ou seja, o arredondamento é feito de maneira que o numero resultante seja par. por exemplo 23,5 ~= 24 mas 22,5 ~= 22
    Também me Java as contas devem ser feitas com BigDecimal que automaticamente utiliza o numero de casas decimais certas e não perde precisão. BigDecimal é equivalente a fazer as contas como nos ensinam na escola , algarismo a algarismo. Não é um processo binário e sim decimal.
    O padrão Money é melhor ainda porque dispensa o BigDecimal através da racionalização de que todas as quantidades são expressas na menor unidade ( centavos ) além de ter a moeda junto ao valor o que é util para encontrar erros quando o seus sistema é multi-moeda, mas util mesmo quando não é.

Deixe um comentário