terça-feira, 6 de maio de 2014

Padrão Money

Resolvi escrever sobre este tópico pois trata-se de um assunto de extrema relevância e que muitos, assim como eu tem dúvidas quando se trata de trabalhar com valores monetários, principalmente quando estamos lidado com dinheiro de terceiros. O problema mais sutil é com o arredondamento. Cálculos monetários muitas vezes são arredondados para a menor unidade da moeda com isso é fácil perder alguns centavos devido aos erros de arredondamento.

Enigma de Matt Foemmel

Suponha que eu tenha que alocar 5 centavos entre duas contas: 70% para uma e 30% para outra. O cálculo matemático resulta em 3,5 centavos e 1,5. Não importa como será arredondado os valores, ou tu ganharás 1 centavo ou perderás 1 centavo.

Como funciona

A idéia básica é ter uma classe Dinheiro com campos para o valor e a moeda corrente. Você pode armazenar a quantidade como um tipo inteiro ou tipo decimal fixo. O tipo decimal é mais fácil para algumas manipulações, o integral para outras. Deve-se evitar completamente qualquer tipo de ponto flutuante, pois isso introduzirá o tipo de problemas de arredondamento cuja finalidade de Dinheiro é evitar.

A solução favorita de Martin Fowler foi essa: ter uma função de alocação na classe Dinheiro. O parâmetro para o alocador é uma lista de números, representando a proporção a ser alocada. O alocador retorna uma lista de objetos dinheiro, garantindo que nenhum centavo seja perdido espalhando-os pelos objetos dinheiro alocados de um modo que, de fora, pareça pseudo-randômico.
Com os números em ponto flutuante descartados, a escolha fica entre inteiros e decimais de ponto fixo, o que em Java se resume a BigDecimal, BigInteger e Long.
class Dinheiro{
   private long quantia;
   private Currency moeda;
}
Usar o long nos permite ter expressões matemáticas mais legíveis. Porém se o número ficar muito grande, ocorrerá um erro de estouro (overflow). A conclusão que se chega é que é necessário a criação de vários construtores para cada tipo numérico:
class Dinheiro{
   public Dinheiro(long quantia, Currency moeda){
      this.moeda = moeda;
      this.quantia = quantia * fatorCentavos( );
   }

   public static final int[ ] centavos = new int[ ] {1,10,100,1000};

   private int fatorCentavos( ){
      return centavos[moeda.getDefaultFractionDigits( )]
   }
}
Diferentes moedas tem diferentes quantias fracionárias. A classe Currency lhe informará o numero de dígitos fracionários em uma classe. O Dinheiro é um Objeto Valor*, então deve ter suas operações de igualdade e código hash sobrescritas para serem baseadas na moeda corrente e na quantia. Se você quiser alocar uma importância em dinheiro para muitos destinatários e não quiser perder centavos, irá precisar de um método de alocação.
class Dinheiro{
   public Dinheiro[ ] alocar (long[ ] razoes){
      long total = 0;
      for(int i = 0; i ;razoes; i++) total += razoes;
      long resto = quantia;
      Dinheiro[ ] resultados = new Dinheiro[razoes.length];
      for(int i = 0; i; resultados.length; i++){
         resultados[ i ] = novoDinheiro(quantia * razoes[ i ] / total);
         resto -= resultados[ i ].quantia;
      }
      for(int i = 0; i; resto; i++)resultados[ i ].quantia++;
      return resultados;
   }
}
Pode-se utilizar isso para resolver o Enigma de Foemmel
class Dinheiro{
   public void testeAlocar( ){
   long[ ] alocacao = {3,7};
   dinheiro[ ]resultado = dinheiro.dolares(0.05).alocar(alocacao);
   equals(Dinheiro.dolares(0.02), resultado[0]);
   equals(Dinheiro.dolares(0.03), resultado[1]);
   }
}
Com isso, uma classe Dinheiro pode ser utilizada para quase todo o cálculo numérico em ambientes OO. A razão principal é encapsular a manipulação do comportamento de arredondamento, o que ajuda a reduzir os problemas de erros de arredondamento. Outra razão para uso de Dinheiro é tornar muito mais fácil o trabalho com diversas moedas. A objeção mais comum a Dinheiro é o desempenho, raramente tenha-se ouvido falar de casos em que ele fez qualquer diferença perceptível, e mesmo nesses casos o encapsulamento, muitas vezes, torna mais fácil a otimização.

*Objeto Valor(Value Object):Um objeto pequeno e simples, como dinheiro ou um intervalo de datas, cuja igualdade não é baseada em identidade.

Referências: Padrões de Arquitetura de Aplicações Corporativas - Martin Fowler - páginas 455 a 457.

Nenhum comentário:

Postar um comentário