Книга: Экстремальное программирование. Разработка через тестирование
Компоновщик (Composite)
Компоновщик (Composite)
Как лучше всего реализовать объект, чье поведение является композицией функций некоторого набора других объектов? Примените шаблон «Самозванец» (Imposter) – заставьте этот объект вести себя подобно тому, как ведут себя отдельные объекты, входящие в набор.
Мой любимый пример основан на двух объектах: 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). Любая из этих метафор недостаточно хорошо соответствует взаимосвязи между вещами в реальном мире, однако все они существенно упрощают код.
Я вынужден был длительное время экспериментировать с шаблоном «Компоновщик» (Composite), прежде чем научился понимать, когда его следует использовать, а когда – нет. Наверное, вы уже поняли, что я не могу предоставить вам однозначных рекомендаций относительно решения проблемы, в каких ситуациях коллекция объектов является просто коллекцией объектов, а в каких это – объект-компоновщик. Хорошая новость состоит в том, что, когда вы достаточно хорошо освоите рефакторинг, вы наверняка сможете обнаружить возникновение дублирования, воспользоваться шаблоном «Компоновщик» (Composite) и обнаружить, что код существенно упростился.
- Команда (Command)
- Объект-значение (Value Object)
- Нуль-объект (Null Object)
- Шаблонный метод (Template Method)
- Встраиваемый объект (Pluggable Object)
- Встраиваемый переключатель (Pluggable Selector)[26]
- Фабричный метод (Factory Method)
- Самозванец (Imposter)
- Компоновщик (Composite)
- Накапливающий параметр (Collecting Parameter)
- Одиночка (Singleton)