Паттерн шаблонный метод java

Паттерн шаблонный метод java

Шаблонный метод — паттерн поведения объектов, определяющий функциональность конктерных методов в рамках лишь абстрактных сущностей.

Условия, Задача, Назначение

Шаблонный метод определяет основу алгоритма в рамках абстрактных классов и методов, а подклассам позволяет переопределять отдельные шаги этого алгоритма (или все сразу), не изменяя, таким образом, его структуру и последовательность в целом.

Мотивация

Рассмотрим каркас приложения, в котором имеются классы Application и Document. Класс Application отвечает за открытие существующих документов, хранящихся во внешнем формате, например в виде файла. Объект класса Document представляет информацию документа после его прочтения из файла.

Приложения, построенные на базе этого каркаса, могут порождать подклассы от классов Application и Document, отвечающие конкретным потребностям.

Например, графический редактор определит подклассы DrawApplication и DrawDocument, а электронная таблица — подклассы SpreadsheetApplication и SpreadsheetDocument.

В абстрактном классе Application определен алгоритм открытия и считывания документа в операции OpenDocument:

Операция OpenDocument определяет все шаги открытия документа. Она проверяет, можно ли открыть документ, создает объект класса Document, добавляет его к набору документов и считывает документ из файла.

Операцию вида OpenDocument мы будем называть шаблонным методом, описывающим алгоритм в терминах абстрактных операций, которые замещены в подклассах для получения нужного поведения. Подклассы класса Application выполняют проверку возможности открытия (CanOpenDocument) и создания документа (DoCreateDocument). Подклассы класса Document считывают документ (DoRead).

Читайте также:  Python http server json response

Шаблонный метод определяет также операцию, которая позволяет подклассам

Application получить информацию о том, что документ вот-вот будет открыт (AboutToOpenDocument). Определяя некоторые шаги алгоритма с помощью абстрактных операций, шаблонный метод фиксирует их последовательность, но позволяет реализовать их в подклассах классов Application и Document.

Признаки применения, использования паттерна Шаблонный метод (Template Method)

  1. Чтобы однократно использовать инвариантные части алгоритма.
    Оставляя реализацию изменяющегося поведения на усмотрение подклассов.
  2. Когда нужно вычленить и локализовать в одном классе поведение.
    Т.е. общую логику, общее поведение для всех подклассов, дабы избежать дублирования кода. Это хороший пример техники «вынесения за скобки с целью обобщения», описанной в работе Уильяма Опдайка (William Opdyke) и Ральфа Джонсона (Ralph Johnson). Сначала идентифицируются различия в существующем коде, а затем они выносятся в отдельные операции. В конечном итоге различающиеся фрагменты кода заменяются шаблонным методом, из которого вызываются новые операции.
  3. Для управления расширениями подклассов.
    Можно определить шаблонный метод так, что он будет вызывать операции-зацепки (hooks) — см. раздел «Результаты» — в определенных точках, разрешив тем самым расширение или изменение функциональности только в этих точках.

Решение

Участники паттерна Шаблонный метод (Template Method)

  1. AbstractClass (Application) — абстрактный класс.
    Определяет абстрактные примитивные операции, замещаемые в конкретных подклассах для реализации шагов алгоритма.
    Реализует шаблонный метод, определяющий скелет алгоритма. Шаблонный метод вызывает примитивные операции, а также операции, определенные в классе AbstractClass или в других объектах.
  2. ConcreteClass (MyApplication) — конкретный класс.
    Реализует примитивные операции, выполняющие шаги алгоритма способом, который зависит от подкласса.

Схема использования паттерна Шаблонный метод (Template Method)

ConcreteClass предполагает, что инвариантные (зафиксированные) шаги алгоритма будут выполнены в AbstractClass.

Вопросы, касающиеся реализации паттерна Шаблонный метод (Template Method)

  1. Использование контроля доступа в C++.
    В этом языке примитивные операции, которые вызывает шаблонный метод, можно объявить защищенными членами. Тогда гарантируется, что вызывать их сможет только сам шаблонный метод. Примитивные операции, которые обязательно нужно замещать, объявляются как чисто виртуальные функции. Сам шаблонный метод замещать не надо, так что его можно сделать невиртуальной функцией-членом.
  2. Сокращение числа примитивных операций.
    Важной целью при проектировании шаблонных методов является значимое сокращение числа примитивных операций, которые должны быть замещены в подклассах. Чем больше операций нужно замещать, тем утомительнее становится программирование клиента.
  3. Соглашение об именах.
    Выделить операции, которые необходимо заместить, можно путем добавления к их именам некоторого префикса. Например, в каркасе МасАрр для приложений на платформе Macintosh имена шаблонных методов начинаются с префикса Do: DoCreateDocument, DoRead и т.д.

Результаты

Шаблонные методы — один из фундаментальных приемов повторного использования кода. Они особенно важны в библиотеках классов, поскольку предоставляют возможность вынести общее поведение в библиотечные классы.

Шаблонные методы приводят к инвертированной структуре кода, которую иногда называют принципом Голливуда, подразумевая часто употребляемую в этой киноимперии фразу «Не звоните нам, мы сами позвоним». В данном случае это означает, что родительский класс сам вызывает операции подкласса, а не наоборот.

Шаблонные методы вызывают операции следующих видов:

  • конкретные операции (либо из класса ConcreteClass, либо из классов клиента).
  • конкретные операции из класса AbstractClass (то есть операции, полезные всем подклассам).
  • примитивные операции (то есть абстрактные операции).
  • фабричные методы (см. паттерн фабричный метод).
  • операции-зацепки (hook operations), реализующие поведение по умолчанию, которое может быть расширено в подклассах. Часто такая операция по умолчанию не делает ничего.

Важно, чтобы в шаблонном методе четко различались операции-зацепки (которые можно замещать) и абстрактные операции (которые нужно замещать). Чтобы повторно использовать абстрактный класс с максимальной эффективностью, авторы подклассов должны понимать, какие операции предназначены для замещения. Обычно об этом явно говорится и в документации к использованию и в комментариях непосредственно в коде.

Подкласс может расширить поведение некоторой операции, заместив ее и явно

вызвав эту операцию из родительского класса:

К сожалению, очень легко забыть о необходимости вызывать унаследованную операцию. У нас есть возможность трансформировать такую операцию в шаблонный метод с целью предоставить родителю контроль над тем, как подклассы расширяют его. Идея в том, чтобы вызывать операцию-зацепку из шаблонного метода в родительском классе. Тогда подклассы смогут переопределить именно эту операцию:

В родительском же классе ParentClass эта операция HookOperation не делает ничего:

Но она замещена в подклассах, которые расширяют поведение:

Источник

Паттерн шаблонный метод java

Предположим, нам поставили задачу написать программу, которая должна нарисовать трехцветный флаг России. При этом она будет действовать по следующему алгоритму:

  • нарисовать флагшток
  • нарисовать первую полосу белого цвета
  • нарисовать вторую полосу синего цвета
  • нарисовать третью полосу красного цвета

Наша программа делает то, что требуется. Далее заказчик попросил написать код для рисования еще одного трехцветного флага — флага Нидерландов.

Но, наш код ужасен! Мы не используем объектно-ориентированный подход, и у нас нарушен DRY-принцип — код дублируется.

Давайте создадим для каждого флага свой класс: RussianFlag и NetherlandsFlag. Рисование конкретным цветом вынесем в методы, которые поместим в класс Colors, что позволит избавиться от дублирования кода.

 class Colors < static void paintWhiteColor() < System.out.println("Полоса белого цвета нарисована"); >static void paintBlueColor() < System.out.println("Полоса синего цвета нарисована"); >static void paintRedColor() < System.out.println("Полоса красного цвета нарисована"); >> 
 class RussianFlag < void drawFlag() < drawFlagpole(); Colors.paintWhiteColor(); Colors.paintBlueColor(); Colors.paintRedColor(); >private void drawFlagpole() < System.out.println("Флагшток нарисован"); >> 
 class NetherlandsFlag < void drawFlag() < drawFlagpole(); Colors.paintRedColor(); Colors.paintWhiteColor(); Colors.paintBlueColor(); >private void drawFlagpole() < System.out.println("Флагшток нарисован"); >> 

Код уже стал лучше, но мы можем вынести рисование флагштока в отдельный класс Flagpole, что бы метод не дублировался. А уже от этого класса будут наследоваться классы для рисования конкретных флагов.

Внесем изменения в классы RussianFlag и NetherlandsFlag: добавим extends Flagpole и уберем метод drawFlagpole:

 class RussianFlag extends Flagpole < void drawFlag() < drawFlagpole(); Colors.paintWhiteColor(); Colors.paintBlueColor(); Colors.paintRedColor(); >> 
 class NetherlandsFlag extends Flagpole < void drawFlag() < drawFlagpole(); Colors.paintRedColor(); Colors.paintWhiteColor(); Colors.paintBlueColor(); >> 

Внезапно мы понимаем, что каждый раз действуем по одному и тому же алгоритму: рисуем флагшток, верхнюю часть флага, среднюю и нижнюю. Этот алгоритм неизменен для рисования флагов с тремя полосами. А что, если нам сделать методы для рисования полос флага абстрактными, а их реализацию делегировать (поручить) классам конкретных флагов? И даже сам алгоритм рисования флага поместить в абстрактный класс в метод drawFlag. А что бы его нельзя было переопределить (изменить) при наследовании, пометить его как final.

Класс Flagpole становится ненужным, поскольку рисование флагштока также можно перенести в абстрактный класс. Итак, класс AbstractThreeRowsFlag будет выглядеть следующим образом:

 abstract class AbstractThreeRowsFlag < abstract void drawUpperLevel(); abstract void drawMiddleLevel(); abstract void drawBottomLevel(); final void drawFlag() < drawFlagpole(); drawUpperLevel(); drawMiddleLevel(); drawBottomLevel(); >private void drawFlagpole() < System.out.println("Флагшток нарисован"); >> 

Метод drawFlag(), содержащий алгоритм рисования флага, является шаблонным методом. Он так называется потому, что задает скелет в виде последовательности шагов, которые будут переопределять (реализовывать), каждый по своему, наследники AbstractThreeRowsFlag. Его подклассы не смогут изменить последовательность шагов, т.к. шаблонный метод помечен, как final и не наследуется.

Как вы уже поняли, для рисования флагов мы только что использовали паттерн под названием «Шаблонный метод» (Template Method), благодаря которому мы избавились от дублирования кода, а так же повысили его универсальность и переиспользуемость.

Обратите внимание, не все методы в шаблонном методе являются абстрактными, например drawFlagpole (рисование флагштока). Из этого можно сделать вывод, что шаблонный метод не обязательно должен состоять только из абстрактных методов, которые необходимо переопределять в подклассах. Он может содержать переменные, какую-то внутреннюю логику и т. д. Но очень часто новички, читая материал по данному паттерну, ошибочно полагают, что шаблонный метод — это метод, который состоит только из абстрактных методов и ничего другого содержать не может. Это и не удивительно, в интернете полно статей с примерами таких методов, что сбивает с толку.

Вернемся к нашим флагам, классы для рисования которых уже выглядят так (про @Override можно прочитать в нашей прошлой статье):

 class RussianFlag extends AbstractThreeRowsFlag < @Override void drawUpperLevel() < Colors.paintWhiteColor(); >@Override void drawMiddleLevel() < Colors.paintBlueColor(); >@Override void drawBottomLevel() < Colors.paintRedColor(); >> 
 class NetherlandsFlag extends AbstractThreeRowsFlag < @Override void drawUpperLevel() < Colors.paintRedColor(); >@Override void drawMiddleLevel() < Colors.paintWhiteColor(); >@Override void drawBottomLevel() < Colors.paintBlueColor(); >> 

Теперь можно нарисовать флаг Югославии, Германии (добавив к нашему набору цветов необходимые: черный и желтый) и любые другие подобные трехцветные флаги, применяя ООП и максимально переиспользуя существующий код.

Источник

Оцените статью