Кент Бек - Экстремальное программирование: Разработка через тестирование стр 10.

Шрифт
Фон

static Money franc(int amount) {

return new Franc(amount);

}

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

$5 * 2 = $10

Сделать переменную amount закрытым (private) членом

Побочные эффекты в классе Dollar?

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

equals()

hashCode()

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

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

5 CHF * 2 = 1 °CHF

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

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

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

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

Валюта?

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

Далее мы планируем перейти к устранению дублирования в методах times(). А сейчас вспомним, что в данной главе мы

• сделали шаг на пути к устранению дублирования – сформировали общую сигнатуру для двух вариантов одного метода – times();

• добавили объявление метода в общий суперкласс;

• освободили тестовый код от ссылок на производные классы, для этого были созданы фабричные методы;

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

9. Потребность в валюте

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

$5 * 2 = $10

Сделать переменную amount закрытым (private) членом

Побочные эффекты в классе Dollar?

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

equals()

hashCode()

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

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

5 CHF * 2 = 1 °CHF

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

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

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

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

Валюта?

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

Есть ли в нашем списке задач какой-либо пункт, который помог бы нам избавиться от этих надоедливых подклассов? Что произойдет, если мы попробуем ввести в нашу программу понятие валюты?

Каким образом мы можем реализовать понятие валюты в данный момент? Черт! Опять я говорю ерунду! Вместо того чтобы снова бить себя линейкой по рукам, попробую перефразировать: каким образом мы можем протестировать понятие валюты в данный момент? Слава богу, мои руки спасены.

Возможно, в будущем нам захочется создать специальный класс валюты, применив шаблон "Приспособленец" (Flyweight Factory), чтобы избежать создания лишних объектов. Однако на текущий момент понятие валюты вполне можно реализовать в виде обычных строк:

public void testCurrency() {

assertEquals("USD", Money.dollar(1). currency());

assertEquals("CHF", Money.franc(1). currency());

}

Прежде всего объявим метод currency() в классе Money:

Money

abstract String currency();

Теперь реализуем этот метод в обоих подклассах:

Franc

String currency() {

return "CHF";

}

Dollar

String currency() {

return "USD";

}

Однако хотелось бы иметь одну и ту же реализацию в обоих подклассах, поэтому сохраним идентификатор валюты в специальном поле класса и просто вернем его значение в методе currency(). (Я начинаю делать рефакторинг быстрее, чтобы сэкономить время. Если вам покажется, что я рассказываю слишком быстро, не стесняйтесь попросить меня замедлиться. Постойте-ка, это же книга, – наверное, мне не стоит слишком уж ускоряться.)

Franc

private String currency;

Franc(int amount) {

this.amount = amount;

currency = "CHF";

}

String currency() {

return currency;

}

То же самое сделаем в классе Dollar:

Dollar

private String currency;

Dollar(int amount) {

this.amount = amount;

currency = "USD";

}

String currency() {

return currency;

}

Теперь мы можем переместить объявление переменной и реализацию метода currency() в базовый класс Money, так как в обоих подклассах они одинаковые:

Money

protected String currency;

String currency() {

return currency;

}

Если переместить строковые констаны "USD" и "CHF" в статические фабричные методы, конструкторы двух производных классов станут идентичными, в результате мы сможем создать общую реализацию.

Вначале добавим в конструктор дополнительный параметр:

Franc

Franc(int amount, String currency) {

this.amount = amount;

this.currency = "CHF";

}

При этом возникают ошибки в двух местах при обращении к конструктору:

Money

static Money franc(int amount) {

return new Franc(amount, null);

}

Franc

Money times(int multiplier) {

return new Franc(amount * multiplier, null);

}

Постойте-ка! Почему это метод Franc.times() вызывает конструктор вместо фабричного метода? Будем ли мы заниматься этим сейчас или отложим любые связанные с этим модификации на более позднее время? В рамках догмы мы должны оставить посторонние дела на потом – не следует прерывать то, чем мы сейчас занимаемся. Однако на практике я иногда позволяю себе отвлечься, но лишь ненадолго, и ни в коем случае я не разрешаю себе прерывать прерывание (этому правилу научил меня Джим Коплаен – Jim Coplien). В данном случае будет лучше, если мы подчистим метод times(), прежде чем продолжить:

Franc

Money times(int multiplier) {

return Money.franc(amount * multiplier);

}

Теперь фабричному методу можно передать значение "CHF":

Money

static Money franc(int amount) {

return new Franc(amount,"CHF");

}

Наконец, мы можем присвоить значение параметра полю класса:

Franc

Franc(int amount, String currency) {

this.amount = amount;

this.currency = currency;

}

Может показаться, что я снова перемещаюсь вперед слишком маленькими шажками. Действительно ли я рекомендую вам работать в таком же темпе? Нет. Я рекомендую вначале научиться работать в таком темпе, а затем самостоятельно определять скорость работы, которая покажется вам наиболее эффективной. Я всего лишь попробовал двигаться вперед большими шагами и на половине дороги допустил глупую ошибку. Запутавшись, я вернулся назад на несколько минут, перешел на пониженную передачу и сделал работу заново, более мелкими шажками. Сейчас я чувствую себя уверенней, поэтому мы можем попробовать внести такие же изменения в класс Dollar за один большой шаг:

Money

static Money dollar(int amount) {

return new Dollar(amount,"USD");

}

Dollar

Dollar(int amount, String currency) {

this.amount = amount;

this.currency = currency;

}

Money times(int multiplier) {

return Money.dollar(amount * multiplier);

}

И это сработало с первого раза. Классно!

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

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

Money

Money(int amount, String currency) {

this.amount = amount;

this.currency = currency;

}

Franc

Franc(int amount, String currency) {

super(amount, currency);

}

Dollar

Dollar(int amount, String currency) {

super(amount, currency);

}

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

$5 * 2 = $10

Сделать переменную amount закрытым (private) членом

Побочные эффекты в классе Dollar?

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

equals()

hashCode()

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

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

5 CHF * 2 = 1 °CHF

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

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

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

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

Валюта?

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

Ваша оценка очень важна

0
Шрифт
Фон

Помогите Вашим друзьям узнать о библиотеке

Скачать книгу

Если нет возможности читать онлайн, скачайте книгу файлом для электронной книжки и читайте офлайн.

fb2.zip txt txt.zip rtf.zip a4.pdf a6.pdf mobi.prc epub ios.epub fb3