позиций в списке параметров, разница между двумя подобными методами становится менее очевидной.

$5 + 1 °CHF = $10, если курс обмена 2:1

$5 + $5 = $10

Операция $5 + $5 возвращает объект Money

Bank.reduce(Money)

Приведение объекта Money с одновременной конверсией валют

Reduce(Bank,String)

Теперь можно приступить к задаче реального обмена одной валюты на другую.

В данной главе мы

• не отметили тест как завершенный, так как не избавились от дублирования;

• чтобы прояснить реализацию, решили двигаться вперед вместо того, чтобы двигаться назад;

• написали тест, чтобы форсировать создание объекта, который, как нам кажется, потребуется в будущем (объект класса Sum);

• ускорили процесс реализации (конструктор класса Sum);

• реализовали код с приведением типов в одном месте, добились успешного выполнения тестов, а затем переместили код туда, где он должен находиться;

• использовали полиморфизм, чтобы избавиться от явной проверки типа (класса).

14. Обмен валюты

$5 + 1 °CHF = $10, если курс обмена 2:1

$5 + $5 = $10

Операция $5 + $5 возвращает объект Money

Bank.reduce(Money)

Приведение объекта Money с одновременной конверсией валют

Reduce(Bank,String)

Изменения, перемены, обмены – их объятия заслуживают внимания (особенно если у вас есть книга с фразой в заголовке «в объятиях изменений» (embrace change))[8]. Впрочем, нас заботит простейшая форма обмена – у нас есть два франка и мы хотим получить один доллар. Это звучит как готовый тест:

public void testReduceMoneyDifferentCurrency() {

Bank bank = new Bank();

bank.addRate("CHF", "USD", 2);

Money result = bank.reduce(Money.franc(2), "USD");

assertEquals(Money.dollar(1), result);

}

Когда я конвертирую франки в доллары, я просто делю значение на два (мы по-прежнему игнорируем все эти неприятные проблемы, связанные с дробными числами). Чтобы сделать полоску зеленой, мы добавляем в код еще одну уродливую конструкцию:

Money

public Money reduce(String to) {

int rate = (currency.equals("CHF") && to.equals("USD"))

? 2

: 1;

return new Money(amount / rate, to);

}

Получается, что класс Money знает о курсе обмена. Это неправильно. Единственным местом, в котором выполняются любые операции, связанные с курсом обмена, должен быть класс Bank. Мы должны передать параметр типа Bank в метод Expression.reduce(). (Вот видите? Мы так и думали, что нам это потребуется. И мы оказались правы.) Вначале меняем вызывающий код:

Bank

Money reduce(Expression source, String to) {

return source.reduce(this, to);

}

Затем меняем код реализаций:

Expression

Money reduce(Bank bank, String to);

Sum

public Money reduce(Bank bank, String to) {

int amount = augend.amount + addend.amount;

return new Money(amount, to);

}

Money

public Money reduce(Bank bank, String to) {

int rate = (currency.equals("CHF") && to.equals("USD"))

? 2

: 1;

return new Money(amount / rate, to);

}

Методы должны быть общедоступными (public), так как все методы интерфейсов должны быть общедоступными (я надеюсь, можно не объяснять, почему).

Теперь мы можем вычислить курс обмена внутри класса Bank:

Bank

int rate(String from, String to) {

return (from.equals("CHF") && to.equals("USD"))

? 2

: 1;

}

И обратиться к объекту bank с просьбой предоставить значение курса обмена:

Money

public Money reduce(Bank bank, String to) {

int rate = bank.rate(currency, to);

return new Money(amount / rate, to);

}

Эта надоедливая цифра 2 снова отсвечивает как в разрабатываемом коде, так и в теле теста. Чтобы избавиться от нее, мы должны создать таблицу обменных курсов в классе Bank и при необходимости обращаться к этой таблице для получения значения обменного курса. Для этой цели мы могли бы воспользоваться хеш-таблицей, которая ставит в соответствие паре валют соответствующий обменный курс. Можем ли мы в качестве ключа использовать двухэлементный массив, содержащий в себе две валюты? Проверяет ли метод Array.equals() эквивалентность элементов массива?

public void testArrayEquals() {

assertEquals(new Object[] {"abc"}, new Object[] {"abc"});

}

Нет. Тест провалился. Придется создавать специальный объект, который будет использоваться в качестве ключа хеш-таблицы:

Pair

private class Pair {

private String from;

private String to;

Pair(String from, String to) {

this.from = from;

this.to = to;

}

}

Мы планируем использовать объекты Pair в качестве ключей, поэтому нам необходимо реализовать методы equals() и hashCode(). Я не собираюсь писать для этого тесты, так как мы разрабатываем код в контексте рефакторинга. Дело в том, что от работоспособности этого кода жестко зависит успешное выполнение существующих тестов. Если код работает неправильно, существующие тесты потерпят неудачу. Однако если бы я программировал в паре с кем-то, кто плохо представлял бы себе направление дальнейшего движения, или если бы логика кода была более сложной, я несомненно приступил бы к разработке специальных тестов.

Pair

public boolean equals(Object object) {

Pair pair = (Pair) object;

return from.equals(pair.from) && to.equals(pair.to);

}

public int hashCode() {

return 0;

}

0 – ужасное хеш-значение, однако такой метод хеширования легко реализовать, стало быть, мы быстрее получим работающий код. Поиск валюты будет осуществляться простым линейным перебором. Позже, когда у нас будет множество валют, мы сможем тщательнее проработать этот вопрос, использовав реальные данные.

Теперь нам нужно место, в котором мы могли бы хранить значения обменных курсов:

Bank

private Hashtable rates= new Hashtable();

Нам также потребуется метод добавления нового курса обмена:

Bank

void addRate(String from, String to, int rate) {

rates.put(new Pair(from, to), new Integer(rate));

}

И метод, возвращающий

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату