Экстремальное программирование. Разработка через тестирование | страница 114



Мой любимый пример основан на двух объектах: Account (счет) и Transaction (транзакция). Этот пример помимо прочего демонстрирует некоторую противоречивость шаблона «Компоновщик» (Composite), но об этом позже. В объекте Transaction хранится изменение величины счета (безусловно, транзакция – это более сложный и интересный объект, однако на данный момент мы ограничимся лишь мизерной долей его возможностей):


Transaction

Transaction(Money value) {

this.value = value;

}


Объект Accout вычисляет баланс счета путем суммирования значений относящихся к нему объектов Transaction:


Account

Transaction transactions[];

Money balance() {

Money sum = Money.zero();

for (int i = 0; i < transactions.length; i++)

sum = sum.plus(transactions[i].value);

return sum;

}


Все выглядит достаточно просто:

• в объектах Transaction хранятся значения;

• в объекте Account хранится баланс.

Теперь самое интересное. У клиента есть несколько счетов, и он хочет узнать общий баланс по всем этим счетам. Первая мысль, которая приходит в голову: создать новый класс OverallAccount, который суммирует балансы для некоторого набора объектов Account. Дублирование! Дублирование!

А что, если классы Account и Transaction будут поддерживать один и тот же интерфейс? Давайте назовем его Holding (сбережения), потому что сейчас мне не удается придумать что-либо лучшее:


Holding

interface Holding

Money balance();


Чтобы реализовать метод balance() в классе Transaction, достаточно вернуть хранящееся в этом классе значение:


Transaction

Money balance() {

return value;

}


Теперь в классе Account можно хранить не транзации, а объекты Holding:


Account

Holding holdings[];

Money balance() {

Money sum = Money.zero();

for (int i = 0; i < holdings.length; i++)

sum = sum.plus(holdings[i].balance());

return sum;

}


Проблема, связанная с созданием класса OverallAccount, испарилась в воздухе. Объект OverallAccount – это просто еще один объект Account, в котором хранятся не транзакции, а другие объекты Account.

Теперь о противоречивости. В приведенном примере хорошо чувствуется запах шаблона «Компоновщик» (Composite). В реальном мире транзакция не может содержать в себе баланс. В данном случае программист идет на уловку, которая совершенно не логична с точки зрения всего остального мира. Вместе с тем преимущества подобного дизайна неоспоримы, и ради этих преимуществ можно пожертвовать некоторым концептуальным несоответствием. Если присмотреться, подобные несоответствия встречаются нам на каждом шагу: папки (Folders), в которых содержатся другие папки (Folders), наборы тестов (TestSuites), в которых содержатся другие наборы тестов (TestSuites), рисунки (Drawings), в которых содержатся другие рисунки (Drawings). Любая из этих метафор недостаточно хорошо соответствует взаимосвязи между вещами в реальном мире, однако все они существенно упрощают код.