Предположим, вы добавили подкласс класса TestCase и в этот подкласс вы добавили тестовый метод. В следующий раз, когда будут выполняться все тесты, добавленный вами тестовый метод также должен быть выполнен. (Во мне опять проснулась привычка действовать в стиле TDD – должно быть, вы заметили, что предыдущее предложение – это эскиз теста, который я, наверное, написал бы, если бы не был занят работой над данной книгой.) К сожалению, в большинстве реализаций xUnit, равно как и в большинстве IDE, не поддерживается стандартный механизм запуска абсолютно всех тестов, поэтому в каждом пакете необходимо определить класс AllTests, который реализует статический метод suite(), возвращающий объект класса TestSuite. Вот класс AllTests для «денежного» примера:
public class AllTests {
public static void main(String[] args) {
junit.swingui.TestRunner.run(AllTests.class);
}
public static Test suite() {
TestSuite result = new TestSuite("TFD tests");
result.addTestSuite(MoneyTest.class);
result.addTestSuite(ExchangeTest.class);
result.addTestSuite(IdentityRateTest.class);
return result;
}
}
Вы также должны включить в класс AllTests() метод main(), благодаря чему класс можно будет запустить напрямую из IDE или из командной строки.
30. Шаблоны проектирования
В чем заключается основная идея шаблонов? Нам кажется, что мы постоянно сталкиваемся с разнообразными, неповторяющимися проблемами, однако на деле оказывается, что большая часть проблем, которые нам приходится решать, обусловлена используемыми нами инструментами, но не основной задачей, которая перед нами стоит[24]. Если исходить из этого предположения, то можно найти (и мы действительно находим) общие проблемы со стандартными решениями, несмотря на все разнообразие контекстов, в рамках которых нам приходится работать.
Использование объектов для организации вычислений – это один из лучших примеров стандартного решения, направленного на устранение множества общих проблем, с которыми программистам приходится сталкиваться при разработке самого разнообразного программного обеспечения. Колоссальный успех шаблонов проектирования (design patterns) является доказательством общности проблем, с которыми сталкиваются программисты, использующие объектно-ориентированные языки программирования[25]. Книга Design Patterns («Паттерны проектирования») имела большой успех, однако ее популярность стала причиной сужения взгляда на шаблоны проектирования. Что я имею в виду? Книга рассматривает дизайн как фазу разработки программы, однако авторы совершенно не учитывают, что рефакторинг – это мощный инструмент формирования дизайна. Дизайн в рамках TDD требует несколько иного взгляда на шаблоны проектирования.
В данной главе я расскажу о нескольких полезных шаблонах проектирования. Безусловно, мое изложение не претендует на полноту. Представленная здесь информация может оказаться полезной при изучении рассматриваемых в книге примеров. Вот краткое перечисление рассмотренных здесь шаблонов:
• «Команда» (Command) – обращение к некоторому коду представляется в виде объекта, а не в виде простого сообщения;
• «Объект-значение» (Value Object) – после создания объекта его значение никогда не меняется, благодаря этому удается избежать проблем, связанных с наложением имен (aliasing);
• «Нуль-объект» (Null Object) – соответствует базовому случаю вычислений объекта;
• «Шаблонный метод» (Template Method) – представляет собой инвариантную последовательность операций, определяемую при помощи абстрактных методов, которые можно переопределить с помощью наследования;
• «Встраиваемый объект» (Pluggable Object) – представляет собой вариацию в виде объекта с двумя реализациями или большим их количеством;
• «Встраиваемый переключатель» (Pluggable Selector) – позволяет избежать создания многочисленных подклассов путем динамического обращения к различным методам для различных экземпляров класса;
• «Фабричный метод» (Factory Method) – вместо конструктора для создания объекта используется специальный метод;
• «Самозванец» (Imposter) – представляет собой вариацию путем создания новой реализации существующего протокола;
• «Компоновщик» (Composite) – композиция объектов ведет себя так же, как один объект;
• «Накапливающий параметр» (Collecting Parameter) – результаты вычислений, выполняемых в разных объектах, накапливаются в специальном объекте, который передается объектам, выполняющим вычисления, в качестве параметра.
В табл. 30.1 описывается, на каких этапах TDD используется тот или иной шаблон проектирования.
Таблица 30.1. Использование шаблонов проектирования при разработке через тестирование (TDD)
Команда (Command)Что делать, если выполнение некоторой операции представляет собой нечто более сложное, чем простое обращение к методу? Создайте объект, соответствующий этой операции, и обратитесь к этому объекту.
Передача сообщений – это отличный механизм. Языки программирования делают передачу сообщений синтаксически простым действием, а среды разработки позволяют с легкостью манипулировать сообщениями (например, автоматическое выполнение рефакторинга по переименованию метода). Однако в некоторых случаях простой передачи сообщения недостаточно.
Например, представьте, что вы хотели бы занести в журнал запись о том, что сообщение было передано. Для этой цели можно воспользоваться средствами языка (например, методы-обертки), однако простые операции журналирования – это далеко не все, в чем вы можете нуждаться. Представьте, что мы хотим вызвать некоторую процедуру, но несколько позднее. Для этой цели можно создать новый программный поток, сразу же приостановить его работу, а затем, когда это потребуется, запустить его. Однако в подобной ситуации нам придется иметь дело с параллельными потоками, а это может оказаться слишком тяжеловесным подходом.
Для выполнения операций с подобными дополнительными условиями зачастую требуются сложные затратные механизмы. Однако в большинстве случаев мы можем избежать излишней сложности и лишних затрат. Проблему вызова можно решить с помощью более конкретной и гибкой формы, чем сообщение. Для этого достаточно создать специальный объект. Создайте объект, представляющий собой вызов операции. Занесите в этот объект все необходимые параметры операции. Когда операция готова к выполнению, используйте для этого универсальный протокол, например метод run().
Отличным примером использования данного подхода является интерфейс Runnable языка Java:
Runnable
interface Runnable
public abstract void run();
В рамках реализации метода run() вы можете делать все, что вам нравится. К сожалению, Java не поддерживает синтаксически легковесного способа создания объектов Runnable и обращения к этим объектам, поэтому они не используются так часто, как их эквиваленты в других языках (блоки или лямбда-выражения в Smalltalk/Ruby или LISP).
Объект-значение (Value Object)Как следует спроектировать объект, который будет широко использоваться, но для которого идентификация не имеет особого значения? Настройте состояние объекта в момент его создания и никогда не меняйте его. В результате выполнения любых операций с данным объектом должен получаться новый объект.
Объектно-ориентированный подход – это великолепная вещь. Надеюсь, я имею право написать эту фразу в данной книге. Объекты являются отличным способом организации логики для последующего понимания и