• сделали второй класс (Franc) подклассом общего суперкласса (Money);
• унифицировали две реализации метода equals() и удалили избыточную реализацию в классе Franc.
7. Яблоки и апельсины
$5 + 1 °CHF = $10, если курс обмена 2:1
Округление денежных величин?
equals()
hashCode()
Равенство значению null
Равенство объектов
Дублирование Dollar/Franc
Общие операции times()
Сравнение франков (Franc) и долларов (Dollar)
В конце главы 6 перед нами встал интересный вопрос: что будет, если мы сравним франки и доллары? Мы немедленно добавили соответствующий пункт в список предстоящих задач. Нам никак не избавиться от этой мысли. И в самом деле, что произойдет?
public void testEquality() {
assertTrue(new Dollar(5). equals(new Dollar(5)));
assertFalse(new Dollar(5). equals(new Dollar(6)));
assertTrue(new Franc(5). equals(new Franc(5)));
assertFalse(new Franc(5). equals(new Franc(6)));
assertFalse(new Franc(5). equals(new Dollar(5)));
}
Тест завершается неудачей. С точки зрения написанного кода доллары – это франки. Прежде чем у наших швейцарских клиентов глаза вылезут на лоб, давайте попробуем исправить код. Код сравнения двух денежных значений должен убедиться в том, что он не сравнивает доллары с франками. Для этого мы должны проверить классы сравниваемых объектов – два объекта класса Money считаются равными только в том случае, если у них равны значения amount и классы.
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount
&& getClass(). equals(money.getClass());
}
Подобное использование классов, по правде сказать, отдает неприятным запашком. Предпочтительнее было бы использовать критерий из области финансов, а не из области объектов языка Java. Однако на текущий момент в нашей программе еще нет ничего, что соответствовало бы финансовому понятию «валюта», и пока я не вижу достаточно весомой причины, чтобы вводить в программу подобное понятие. Поэтому пока оставим код таким, какой он есть.
$5 + 1 °CHF = $10, если курс обмена 2:1
Округление денежных величин?
hashCode()
Равенство значению null
Равенство объектов
Дублирование Dollar/Franc
Общие операции times()
Валюта?
Теперь пришла пора избавиться от дублирующегося кода в методах times(), после этого мы сможем перейти к реализации смешанной валютной арифметики. Однако прежде, чем двинуться дальше, подведем итоги данной главы:
• мы превратили мучающее нас сомнение в тест;
• добились успешного выполнения теста приемлемым, но не идеальным способом – getClass();
• решили не добавлять в программу дополнительной логики, пока у нас не появится более весомая мотивация.
8. Создание объектов
$5 + 1 °CHF = $10, если курс обмена 2:1
Округление денежных величин?
hashCode()
Равенство значению null
Равенство объектов
Дублирование Dollar/Franc
Общие операции times()
Валюта?
Две разные реализации метода times() выглядят на удивление похоже:
Franc
Franc times(int multiplier) {
return new Franc(amount * multiplier)
}
Dollar
Dollar times(int multiplier) {
return new Dollar(amount * multiplier)
}
Мы можем сделать их еще более похожими, изменив тип возвращаемого значения на Money:
Franc
Money times(int multiplier) {
return new Franc(amount * multiplier)
}
Dollar
Money times(int multiplier) {
return new Dollar(amount * multiplier)
}
Следующий шаг менее очевиден. Два подкласса, производных от класса Money, мало чем отличаются друг от друга. Возникает желание избавиться от них. Однако мы не можем сделать это за один большой шаг, так как это нельзя будет назвать наглядной демонстрацией методики TDD.
Но что же делать? Полагаю, мы сможем приблизиться к решению задачи об уничтожении подклассов, если избавимся от прямых ссылок на подклассы. Для этого мы можем добавить в класс Money фабричный метод, который возвращал бы объект класса Dollar. Этот метод можно было бы использовать следующим образом: