Округление денежных величин?

equals()

hashCode()

Равенство значению null

Равенство объектов

5 CHF * 2 = 1 °CHF

Дублирование Dollar/Franc

Общие операции equals()

Общие операции times()

Сравнение франков (Franc) и долларов (Dollar)

Валюта?

Нужен ли тест testFrancMultiplication()?

Тест testDifferentClassEquality() служит доказательством того, что, сравнивая объекты, мы сравниваем различные валюты, но не различные классы. Этот тест имеет смысл только в случае, если в программе существует несколько различных классов. Однако мы уже избавились от класса Dollar и намерены точно так же избавиться от класса Franc. Иными словами, в нашем распоряжении останется только один денежный класс: Money. С учетом наших намерений, тест testDifferentClassEquality() оказывается для нас излишней обузой. Мы удалим его, а затем избавимся от класса Franc.

Обратите также внимание, что в программе присутствуют отдельные тесты для проверки умножения франков на доллары. Если заглянуть в код, можно увидеть, что на текущий момент логика метода, реализующего умножение, не зависит от типа валюты (зависимость была бы только в случае, если бы мы использовали два различных класса). То есть мы можем удалить функцию testFrancMultiplication(), не опасаясь, что потеряем уверенность в правильности работы системы.

Итак, в нашем распоряжении единый денежный класс, и мы готовы приступить к реализации сложения.

Но сначала подведем итоги. В этой главе мы

• закончили потрошить производные классы и избавились от них;

• удалили тесты, которые имели смысл только при использовании старой структуры кода, но оказались избыточными в коде с новой структурой.

12. Сложение, наконец-то

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

Наступил новый день, и я заметил, что список задач переполнен вычеркнутыми пунктами. Лучше всего переписать оставшиеся не зачеркнутыми пункты в новый свежий список. (Я люблю физически копировать пункты из старого списка в новый список. Если в старом списке много мелких недоделанных задач, вместо того, чтобы копировать их в новый список, я просто добавляю в программу соответствующий код. В результате из-за моей лени куча мелочей, которая могла бы расти со временем, просто исчезает. Используйте свои слабости.)

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

$5 + $5 = $10

Пока что я не представляю себе, как можно реализовать смешанное сложение долларов и франков, поэтому предлагаю начать с более простой задачи: $5 + $5 = $10.

public void testSimpleAddition() {

Money sum = Money.dollar(5). plus(Money.dollar(5));

assertEquals(Money.dollar(10), sum);

}

Мы могли бы подделать реализацию, просто вернув значение Money.dollar(10), однако в данном случае реализация кажется очевидной. Давайте попробуем:

Money

Money plus(Money addend) {

return new Money(amount + addend.amount, currency);

}

(Далее я буду ускорять процесс разработки, чтобы сэкономить бумагу и сохранить ваш интерес. Там, где дизайн не очевиден, я буду подделывать реализацию и выполнять рефакторинг. Я надеюсь, что благодаря этому вы увидите, каким образом в TDD выполняется контроль над величиной шагов.)

Сказав, что планирую увеличить скорость, я немедленно замедляю процесс разработки. Однако я не планирую замедлять процесс написания кода, который обеспечивает успешное тестирование. Я планирую замедлить процесс написания самих тестов. Некоторые ситуации и некоторые тесты требуют тщательного обдумывания. Каким образом мы планируем представить арифметику со смешанными валютами? Это как раз тот случай, когда требуется тщательное обдумывание.

Наиболее важное и сложное ограничение, с которым нам приходится иметь дело, заключается в том, что мы не хотим, чтобы код нашей системы знал о существовании каких-либо валют. Нам хотелось бы, чтобы система имела дело с деньгами и не зависела от того, в какой валюте они представлены. Возможная стратегия состоит в том, чтобы немедленно преобразовывать любые денежные значения в некоторую единую валюту (попробуйте угадать, какая валюта является самой любимой у американских программистов). Однако подобное решение не позволит нам с легкостью варьировать соотношения (курсы обмена) между различными валютами.

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

Решение основано на объектах. Если имеющийся объект ведет себя не так, как нам хотелось бы, мы создаем еще один объект, обладающий точно таким же внешним протоколом, но отличающейся внутренней реализацией. Этот шаблон называется «Самозванец» (Imposter).

Возможно, многим это покажется хиромантией. Каким образом в данной ситуации можно использовать шаблон «Самозванец»? Однако я не собираюсь шутить над вами – не существует формулы, позволяющей генерировать гениальные дизайнерские решения. Решение проблемы было придумано Уордом Каннигемом десятилетие назад. Я еще не встречал человека, который независимо от Уорда придумал бы нечто подобное. К сожалению, методика TDD не гарантирует генерацию гениальных идей. Вместе с тем благодаря TDD вы имеете тесты, формирующие вашу уверенность в коде, а также тщательно вылизанный код, – все это является хорошей почвой для возникновения идеи и ее воплощения в реальность.

Итак, что же является решением в нашем случае? Предлагается создать объект, который ведет себя как объект Money, однако соответствует сумме двух объектов Money. Чтобы объяснить эту идею, я пробовал использовать несколько разных метафор. Например, можно рассматривать сумму различных денежных величин как бумажник. В один бумажник можно положить несколько банкнот разных валют и разных достоинств.

Еще одна метафора: выражение. Имеется в виду математическое выражение, например: (2 + 3) * 5. В нашем случае мы имеем дело с денежными величинами, поэтому выражение может быть таким: ($2 + 3 CHF) * 5. Класс Money – это атомарная форма выражения. В результате выполнения любых операций над денежными величинами получается объект класса Expression. Одним из таких объектов может быть объект Sum[7]. После того как операция (например, сложение нескольких значений в разных валютах) выполнена, полученный объект Expression можно привести к некоторой заданной валюте. Преобразование к некоторой валюте осуществляется на основании набора курсов обмена.

Как выразить эту метафору в виде набора тестов? Прежде всего, мы знаем, к чему мы должны прийти:

public void testSimpleAddition() {

assertEquals(Money.dollar(10), reduced);

}

Переменная

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

0

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

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