Мы хотели бы добавить в программу новый класс Money, однако мы не можем этого сделать, так как для тестирования нам нужен экземпляр класса Dollar. Чтобы решить проблему, достаточно добавить в программу дополнительный уровень перенаправления – метод, который будет возвращать объект иного класса. В этом случае мы сможем оставить выражения assert без изменений:
public void testMultiplication() {
Dollar five = Money.dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}
Money
static Dollar dollar(int amount) {
return new Dollar(amount);
}
Такой метод называется
Недостаток этого шаблона заключается в том, что предназначение фабричного метода не очевидно: вы должны помнить о том, что этот метод создает объекты, вместе с тем это обычный метод, а не конструктор. Фабричный метод следует использовать только тогда, когда вы действительно нуждаетесь в гибкости, которую он обеспечивает. В противном случае для создания объектов вполне можно использовать обычные конструкторы.
Как можно добавить в программу новую вариацию некоторой функциональности? Создайте новый объект с точно таким же протоколом, как и существующий объект, но с отличающейся реализацией.
При использовании процедурно-ориентированного подхода для решения подобной задачи в программу требуется добавить как минимум один условный оператор. Как было продемонстрировано ранее, при обсуждении шаблона «Встраиваемый переключатель» (Pluggable Selector), такие условные операторы имеют тенденцию плодиться подобно саранче. Чтобы избавиться от дублирования, требуется полиморфизм.
Представьте, что у вас уже есть необходимая инфраструктура. У вас уже есть объект, который реализует необходимую функциональность. Теперь вы хотите, чтобы ваша система делала нечто отличающееся. Если вы обнаружили очевидное место для добавления оператора if и при этом не возникает дублирования какой-либо существующей логики, действуйте смело и решительно. Однако зачастую для добавления вариации требуется внести изменения в код нескольких методов.
Если вы работаете в стиле TDD, решение об использовании самозванца может возникнуть исходя из разных предпосылок. Иногда вы пишете тест и у вас возникает желание реализовать новый сценарий. Однако ни один из существующих объектов не выражает того, что вы хотите выразить. Представьте, что мы тестируем графический редактор и нам уже удалось реализовать корректное рисование прямоугольников:
testRectangle() {
Drawing d = new Drawing();
d. addFigure(new RectangleFigure(0, 10, 50, 100));
RecordingMedium brush = new RecordingMedium();
d. display(brush);
assertEquals("rectangle 0 10 50 100\n", brush.log());
}
Теперь мы хотим реализовать рисование овалов. В данном случае необходимость применения шаблона «Самозванец» (Imposter) очевидна: заменяем RectangleFigure на OvalFigure.
testOval() {
Drawing d = new Drawing();
d. addFigure(new OvalFigure(0, 10, 50, 100));
RecordingMedium brush = new RecordingMedium();
d. display(brush);
assertEquals("oval 0 10 50 100\n", brush.log());
}
Как правило, чтобы увидеть необходимость использования этого шаблона еще до начала разработки кода, требуется озарение. Именно озарением можно назвать момент, когда Уорд Каннингэм решил, что вектор объектов Money может вести себя так же, как одиночный объект Money. Сначала можно подумать, что они различаются, однако после вы понимаете, что они одинаковы.
Вот два примера использования «Самозванец» (Imposter) в процессе рефакторинга:
• «Нуль-объект» (Null Object) – вы можете рассматривать отсутствие данных в точности так же, как и присутствие данных;
• «Компоновщик» (Composite) – вы можете рассматривать коллекцию объектов как одиночный объект.
Решение об использовании «Самозванец» (Imposter) в процессе рефакторинга принимается для устранения дублирования, впрочем, целью любого рефакторинга является устранение дублирования.
Как лучше всего реализовать объект, чье поведение является композицией функций некоторого набора других объектов? Примените шаблон «Самозванец» (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++)
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии