Вот два примера использования «Самозванец» (Imposter) в процессе рефакторинга:
• «Нуль-объект» (Null Object) – вы можете рассматривать отсутствие данных в точности так же, как и присутствие данных;
• «Компоновщик» (Composite) – вы можете рассматривать коллекцию объектов как одиночный объект.
Решение об использовании «Самозванец» (Imposter) в процессе рефакторинга принимается для устранения дублирования, впрочем, целью любого рефакторинга является устранение дублирования.
Компоновщик (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) и обнаружить, что код существенно упростился.
Накапливающий параметр (Collecting Parameter)
Как можно сформировать результат операции, если она распределена между несколькими объектами? Используйте параметр, в котором будут накапливаться результаты операции.
Простым примером является интерфейс java.io.Externalizable. Метод writeExternal этого интерфейса осуществляет запись объекта и всех объектов, на которые ссылается данный объект. Чтобы обеспечить общую запись, все записываемые объекты должны взаимодействовать друг с другом, поэтому методу передается параметр – объект класса ObjectOutput, – в котором осуществляется накопление:
Читать дальше
Конец ознакомительного отрывка
Купить книгу