Если вы обнаружили два варианта последовательности в двух подклассах, вы должны попытаться постепенно приблизить их друг к другу. После того как вы отделите различающиеся части и выделите общую часть, то, что останется, и есть шаблонный метод. После этого вы можете переместить шаблонный метод в суперкласс и избавиться от дублирования.
Встраиваемый объект (Pluggable Object)
Как можно выразить несколько разных вариантов поведения кода? Проще всего использовать явный условный оператор:
if(circle) then {
… код, относящийся к circle.
} else {
… код, не относящийся к circle
}
Однако подобный корявый код имеет тенденцию распространяться по всей программе. Если для определения разницы между окружностями и не окружностями вы будете использовать условный оператор хотя бы в одном месте вашего кода, с большой долей уверенности можно сказать, что позднее подобный оператор придется добавить также в другом месте, затем в третьем и т. д.
Вторая по важности задача TDD – устранение дублирования, поэтому вы должны подавить угрозу распространения явных условных операторов в зародыше. Если вы видите, что одно и то же условие проверяется в двух разных местах вашего кода, значит, настало время выполнить базовое объектно-ориентированное преобразование: «Встраиваемый объект» (PluggableObject).
Иногда обнаружить необходимость применения этого шаблона не так просто. Один из самых любимых мною примеров использования встраиваемого объекта был придуман мною и Эрихом Гаммой. Представьте, что мы занимаемся разработкой графического редактора. Если вы когда-нибудь занимались чем-либо подобным, должно быть, вы знаете, что операция выделения объектов обладает несколько усложненной логикой. Если указатель мыши находится над графической фигурой и пользователь нажимает кнопку мыши, значит, последующие перемещения мыши приводят к перемещению фигуры, а при отпускании кнопки мыши выбранная фигура остается на новом месте. Если указатель мыши не находится над какой-либо фигурой, значит, нажав кнопку, пользователь выделяет несколько фигур, последующие перемещения мыши приводят к изменению размера прямоугольника выделения, а при отпускании кнопки мыши фигуры внутри прямоугольника выделения становятся выделенными. Изначальный код выглядит примерно так:
SelectionTool
Figure selected;
public void mouseDown() {
selected = findFigure();
if (selected!= null)
select(selected);
}
public void mouseMove() {
if (selected!= null)
move(selected);
else
moveSelectionRectangle();
}
public void mouseUp() {
if (selected == null)
selectAll();
}
В глаза бросаются три похожих условных оператора (я же говорил, что они плодятся, как мухи). Что делать, чтобы избавиться от них? Создаем встраиваемый объект, SelectionMode, обладающий двумя реализациями: SingleSelection и MultipleSelection.
SelectionTool
SelectionMode mode;
public void mouseDown() {
selected = findFigure();
if (selected!= null)
mode = SingleSelection(selected);
else
mode = MultipleSelection();
}
public void mouseMove() {
mode.mouseMove();
}
public void mouseUp() {
mode.mouseUp();
}
В языках с явными интерфейсами вы обязаны реализовать интерфейс с двумя (или больше) встраиваемыми объектами.
Встраиваемый переключатель (Pluggable Selector) [26]
Как обеспечить различающееся поведение разных экземпляров одного и того же класса? Сохраните имя метода в переменной и динамически обращайтесь к этому методу.
Что делать, если у вас есть десять подклассов одного базового класса и в каждом из них реализован только один метод? Может оказаться, что создание подклассов – это слишком тяжеловесный механизм для реализации столь небольших различий в поведении объектов.
abstract class Report {
abstract void print();
}
class HTMLReport extends Report {
void print() {…
}
}
class XMLReport extends Report {
void print() {…
}
}
Альтернативное решение: создать единственный класс с оператором switch. В зависимости от значения поля происходит обращение к разным методам. Однако в этом случае имя метода упоминается в трех местах:
• при создании экземпляра;
• в операторе switch;
• в самом методе.
abstract class Report {
String printMessage;
Report(String printMessage) {
this.printMessage = printMessage;
}
void print() {
switch (printMessage) {
case "printHTML":
printHTML();
break;
case "printXML":
printXML():
break;
}
};
void printHTML() {
}
void printXML() {
}
}
Каждый раз, когда вы добавляете новую разновидность печати, вы должны позаботиться о добавлении нового метода печати и редактировании оператора switch.
Шаблон «Встраиваемый переключатель» (Pluggable Selector) предлагает динамически обращаться к методу с использованием механизма рефлексии:
Читать дальше
Конец ознакомительного отрывка
Купить книгу