Множество приложений оперирует деньгами, но в большинстве языков программирования, в том числе и в Java, нет стандартного класса Money. Многие просто хранят денежные величины в переменный с плавающей точкой, но это плохое решение, так как всегда возникают проблемы с округлением, которые приходится каждый раз решать по-разному. Кроме того, деньги могут измеряться в разных величинах (рублях, долларах, евро и других). Нельзя складывать или сравнивать евро с рублями — это неминуемо вызовет ошибку в результате.
При реализации класса денег нужно первым делом выбрать тип данных для хранения величины денежной суммы. Первым делом в голову приходит, что нужно использовать тип с плавающей точкой. Это не будет хорошим решением. Дело в том, что вычисление с плавающей точкой не всегда приводят к желательному результату. Попробуйте выполнить следующий пример:
Поэтому лучше выбрать целочисленный тип и выражать его в наименьших единицах для выбранной валюты: в копейках или центах. Для валюты лучше использовать стандартный для Java тип данных Currency.
Класс, который получился у меня:
И тест JUnit к нему:
Статья написана по мотивам книги Мартина Фаулера «Архитектура корпоративных приложений».
При реализации класса денег нужно первым делом выбрать тип данных для хранения величины денежной суммы. Первым делом в голову приходит, что нужно использовать тип с плавающей точкой. Это не будет хорошим решением. Дело в том, что вычисление с плавающей точкой не всегда приводят к желательному результату. Попробуйте выполнить следующий пример:
double val = 0.00;
for (int i = 0; i < 10; i++)
val += 0.10;
System.out.println( val == 1.00 );* This source code was highlighted with Source Code Highlighter.
Поэтому лучше выбрать целочисленный тип и выражать его в наименьших единицах для выбранной валюты: в копейках или центах. Для валюты лучше использовать стандартный для Java тип данных Currency.
Класс, который получился у меня:
package money;
import java.util.Currency;
import java.util.Locale;
import org.junit.Assert;
/**
* Created by IntelliJ IDEA.
* User: Anthony
* Date: 14.09.2008
* Time: 15:25:26
* To change this template use File | Settings | File Templates.
*/
public class Money {
private long amount;
private Currency currency;
/**
* return Числовое значение денег.
*/
public double getAmount() {
return (double)amount / centFactor();
}
/**
* return Текущую валюту
*/
public Currency getCurrency() {
return currency;
}
public Money(long amount, Currency currency )
{
this.currency = currency;
this.amount = amount * centFactor();
}
public Money(double amount, Currency currency)
{
this.currency = currency;
this.amount = Math.round( amount * centFactor() );
}
private Money()
{
}
public static Money dollars(double amount)
{
return new Money(amount, Currency.getInstance( Locale.US ) );
}
public static Money locale(double amount)
{
return new Money(amount, Currency.getInstance( Locale.getDefault() ) );
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
if (amount != money.amount) return false;
if (currency != null? !currency.equals(money.currency): money.currency != null) return false;
return true;
}
public int hashCode() {
int result;
result = (int) (amount ^ (amount >>> 32));
result = 31 * result + (currency != null? currency.hashCode(): 0);
return result;
}
/**
* Функция складывает деньги.
* param other с чем сложить.
* return Результат сложения.
*/
public Money add(Money other)
{
assertSameCurrencyAs(other);
return newMoney(amount + other.amount);
}
/**
* Функция вычитает деньги.
* param other вычистаемое.
* return Результат вычитания.
*/
public Money subtract(Money other)
{
assertSameCurrencyAs(other);
return newMoney(amount — other.amount);
}
/**
* Функция умнажает деньги на коэффициент.
* param arg вычистаемое.
* return Результат умножения.
*/
public Money multiply(double arg)
{
return new Money(getAmount() * arg, currency);
}
public int compareTo(Object other)
{
return compareTo((Money)other);
}
/**
* Фунция сравнения денег.
* param other с чем сравнить
* return
* -1 меньше<br>
* 0 равно<br>
* 1 больше<br>
*/
public int compareTo(Money other)
{
assertSameCurrencyAs(other);
if(amount < other.amount)
return -1;
if(amount == other.amount)
return 0;
return 1;
}
/**
* Больше ли деньги.
* param other с чем сравнивать.
* return True, если больше.
*/
public boolean greaterThan(Money other)
{
return (compareTo(other) > 0);
}
/**
* Разделить деньги на несколько частей.
* param n количество частей.
* return Массив разделенных денег.
*/
public Money[] allocate(int n)
{
Money lowResult = newMoney(amount/n);
Money highResult = newMoney(lowResult.amount + 1);
Money[] results = new Money[n];
int remainder = (int)amount % n;
for (int i = 0; i < remainder; i++)
results[i] = highResult;
for (int i = remainder; i < n; i++)
results[i] = lowResult;
return results;
}
/**
* Разделяет деньги на неравный части.
* param ratios пропорция для разделения.
* return Массив разделенных денег.
*/
public Money[] allocate(long[] ratios) {
long total = 0;
for (int i = 0; i < ratios.length; i++)
total += ratios[i];
long remainder = amount;
Money[] results = new Money[ratios.length];
for (int i = 0; i < results.length; i++)
{
results [i] = newMoney(amount * ratios[i] / total);
remainder -= results[i].amount;
}
for (int i = 0; i < remainder; i++) {
results[i].amount++;
}
return results;
}
private static final int [ ] cents = new int[] { 1, 10, 100, 1000 };
private int centFactor()
{
return cents[currency.getDefaultFractionDigits()];
}
private void assertSameCurrencyAs(Money arg)
{
Assert.assertEquals(«money math mismatch», currency, arg.currency);
}
private Money newMoney(long amount)
{
Money money = new Money();
money.currency = this.currency;
money.amount = amount;
return money;
}
}
* This source code was highlighted with Source Code Highlighter.
И тест JUnit к нему:
package test.money;
import junit.framework.Test;
import junit.framework.TestSuite;
import junit.framework.TestCase;
import junit.framework.Assert;
import money.Money;
/**
* Money Tester.
*
* author <Authors name>
* since <pre>09/14/2008</pre>
* version 1.0
*/
public class MoneyTest extends TestCase {
public MoneyTest(String name) {
super(name);
}
public void setUp() throws Exception {
super.setUp();
}
public void tearDown() throws Exception {
super.tearDown();
}
/**
*
* Method: add(Money other)
*
*/
public void testAdd() throws Exception {
Money m1 = Money.dollars( 1.316 );
Money m2 = Money.dollars( 1.291 );
Assert.assertEquals( m1.add( m2 ), Money.dollars(2.61) );
}
/**
*
* Method: subtract(Money other)
*
*/
public void testSubtract() throws Exception {
Money m1 = Money.dollars( 1.316 );
Money m2 = Money.dollars( 1.291 );
Assert.assertEquals( m1.subtract( m2 ), Money.dollars(0.03) );
}
/**
*
* Method: compareTo(Object other)
*
*/
public void testCompareTo() throws Exception {
Money m1 = Money.dollars( 1.316 );
Money m2 = Money.dollars( 1.313 );
Assert.assertEquals( m1.compareTo( m2 ), 1 );
}
/**
*
* Method: multiply(double arg)
*
*/
public void testMultiply() throws Exception {
Money m1 = Money.dollars( 1.316 );
Assert.assertEquals( m1.multiply( 1.333 ), Money.dollars(1.76) );
}
/**
*
* Method: allocate(int n)
*
*/
public void testAllocateN() throws Exception {
Money m[] = Money.dollars( 1.35 ).allocate( 3 );
Assert.assertEquals( m[0].add( m[1] ).add( m[2] ), Money.dollars(1.35) );
}
/**
*
* Method: allocate(long[] ratios)
*
*/
public void testAllocateRatios() throws Exception {
long[] allocation = {3,7};
Money[] result = Money.dollars(0.05).allocate(allocation);
assertEquals(Money.dollars(0.02), result[0]);
assertEquals(Money.dollars(0.03), result[1]);
}
public static Test suite() {
return new TestSuite(MoneyTest.class);
}
}
* This source code was highlighted with Source Code Highlighter.
Статья написана по мотивам книги Мартина Фаулера «Архитектура корпоративных приложений».