CodeLAB
на главную карта сайта обратная связь

Популярные задачи:

#Вращение 3D объекта. (33485 hits)
#"C# и платформа .NET" Эндрю Троелсен (Andrew Troelsen, "C# and the .NET platform"), листинги, код, примеры из книги, исходники. (36145 hits)
#Шифрование произвольных данных. (321492 hits)
#Подключение. (24152 hits)
#Логирование в GUI. (29705 hits)
#Просмотр изображения во всплывающем окне. (85977 hits)
#Обертки для массивов. (36654 hits)
#Случайный выбор нескольких несовпадающих значений из множества. (55504 hits)
#Подсветка синтаксиса. (29381 hits)
#Древовидные структуры. (54527 hits)
#Бинарный поиск в массиве и его разновидности. (159884 hits)
#сортировка пузырьком. (148496 hits)
#"Липкие" окна. (29313 hits)
#Вычисление среднего, среднего отклонения, среднеквадратического отклонения и дисперсии заданной выборки. (43987 hits)
#Рисование множества Мандельброта. (41402 hits)
#Создание нестандартного (custom-ного) окна браузера. (33685 hits)
#Вычисление двойного интеграла с использованием MPI. (57098 hits)
#Утилиты. (110293 hits)
#Полезные утилиты, небольшие api и библиотеки и проч.. (66577 hits)
#Доступ ко всем полям и методам. (55087 hits)


Главная >> Каталог задач >> Паттерны >> Поведения >> Шаблонный метод (Template Method)

Шаблонный метод (Template Method)

Aвтор:
Дата:
Просмотров: 107066
реализации(java: 1шт...) +добавить

Имя

«Паттерн Template Method»

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

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

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

Мотивация

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

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

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

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

 открытие, считывание документа [C++]  ссылка
  1. void Application::OpenDocument (const char* name) {
  2. if (!CanOpenDocument(name)) {
  3. // работа с этим документом невозможна
  4. return;
  5. }
  6.  
  7. Document* doc = DoCreateDocument();
  8.  
  9. if (doc) {
  10. _docs->AddDocument(doc);
  11. AboutToOpenDocument(doc);
  12. doc->0pen();
  13. doc->DoRead();
  14. }
  15. }

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

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

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

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), реализующие поведение по умолчанию, которое может быть расширено в подклассах. Часто такая операция по умолчанию не делает ничего.

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

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

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

 пример [C++]  ссылка
  1. void DerivedClass::Operation () {
  2. ParentClass::Operation();
  3. // Расширенное поведение класса DerivedClass
  4. }
  5.  

 

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

 пример [C++]  ссылка
  1. void ParentClass::Operation () {
  2. // Поведение родительского класса ParentClass
  3. HookOperation();
  4. }
  5.  

 

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

 пример [C++]  ссылка
  1. void ParentClass::HookOperation () { }

 

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

 пример [C++]  ссылка
  1. void DerivedClass::HookOperation () {
  2. // расширение в производном классе
  3. }
  4.  

Пример

Паттерн шаблонный метод широко применяется при написании разного рода каркасов.
Для примера рассмотрим основополагающий класс Struts веб-фреймворка: RequestProcessor. Метод этого класса process() запускается при обработке любого запроса на страницу, где задействуется Struts. Очевидно, что такого рода операция подразумевает четкую последовательность шагов обработки http-запроса, включающую все возможные разновидности алгоритма обработки, а также поддержку различных видов запроса и т.д. Но в то же время, чтобы данный фреймворк был довольно гибким необходимо дать возможность расширения этой базовой функциональности алгоритма, его модификации в разумных пределах: т.е. использование фабричных методов и операций-зацепок. Как видно – идеальный случай реализации шаблонного метода. Достаточно слов – просто взглянем на этот метод process():
 RequestProcessor.process() [java]  ссылка
  1. public void process(HttpServletRequest request, HttpServletResponse response)
  2. throws IOException, ServletException {
  3. // Wrap multipart requests with a special wrapper
  4. request = processMultipart(request);
  5.  
  6. // Identify the path component we will use to select a mapping
  7. String path = processPath(request, response);
  8.  
  9. if (path == null) {
  10. return;
  11. }
  12.  
  13. if (log.isDebugEnabled()) {
  14. log.debug("Processing a '" + request.getMethod() + "' for path '"
  15. + path + "'");
  16. }
  17.  
  18. // Select a Locale for the current user if requested
  19. processLocale(request, response);
  20.  
  21. // Set the content type and no-caching headers if requested
  22. processContent(request, response);
  23. processNoCache(request, response);
  24.  
  25. // General purpose preprocessing hook
  26. if (!processPreprocess(request, response)) {
  27. return;
  28. }
  29.  
  30. this.processCachedMessages(request, response);
  31.  
  32. // Identify the mapping for this request
  33. ActionMapping mapping = processMapping(request, response, path);
  34.  
  35. if (mapping == null) {
  36. return;
  37. }
  38.  
  39. // Check for any role required to perform this action
  40. if (!processRoles(request, response, mapping)) {
  41. return;
  42. }
  43.  
  44. // Process any ActionForm bean related to this request
  45. ActionForm form = processActionForm(request, response, mapping);
  46.  
  47. processPopulate(request, response, form, mapping);
  48.  
  49. // Validate any fields of the ActionForm bean, if applicable
  50. try {
  51. if (!processValidate(request, response, form, mapping)) {
  52. return;
  53. }
  54. } catch (InvalidCancelException e) {
  55. ActionForward forward = processException(request, response, e, form, mapping);
  56. processForwardConfig(request, response, forward);
  57. return;
  58. } catch (IOException e) {
  59. throw e;
  60. } catch (ServletException e) {
  61. throw e;
  62. }
  63.  
  64. // Process a forward or include specified by this mapping
  65. if (!processForward(request, response, mapping)) {
  66. return;
  67. }
  68.  
  69. if (!processInclude(request, response, mapping)) {
  70. return;
  71. }
  72.  
  73. // Create or acquire the Action instance to process this request
  74. Action action = processActionCreate(request, response, mapping);
  75.  
  76. if (action == null) {
  77. return;
  78. }
  79.  
  80. // Call the Action instance itself
  81. ActionForward forward =
  82. processActionPerform(request, response, action, form, mapping);
  83.  
  84. // Process the returned ActionForward instance
  85. processForwardConfig(request, response, forward);
  86. }
 
Теперь просто по порядку:
  1. processMultipart() – фабричный метод задействования специальной обработки multipart запросов:
     RequestProcessor.processMultipart() [java]  ссылка
    1. protected HttpServletRequest processMultipart(HttpServletRequest request) {
    2. if (!"POST".equalsIgnoreCase(request.getMethod())) {
    3. return (request);
    4. }
    5.  
    6. String contentType = request.getContentType();
    7.  
    8. if ((contentType != null)
    9. && contentType.startsWith("multipart/form-data")) {
    10. return (new MultipartRequestWrapper(request));
    11. } else {
    12. return (request);
    13. }
    14. }
  2. processPath() – фабричный метод определения запрошенного пути ресурса:
     RequestProcessor.processPath() [java]  ссылка
    1. protected String processPath(HttpServletRequest request,
    2. HttpServletResponse response)
    3. throws IOException {
    4. String path;
    5.  
    6. // For prefix matching, match on the path info (if any)
    7. path = (String) request.getAttribute(INCLUDE_PATH_INFO);
    8.  
    9. if (path == null) {
    10. path = request.getPathInfo();
    11. }
    12.  
    13. if ((path != null) && (path.length() > 0)) {
    14. return (path);
    15. }
    16.  
    17. // For extension matching, strip the module prefix and extension
    18. path = (String) request.getAttribute(INCLUDE_SERVLET_PATH);
    19.  
    20. if (path == null) {
    21. path = request.getServletPath();
    22. }
    23.  
    24. String prefix = moduleConfig.getPrefix();
    25.  
    26. if (!path.startsWith(prefix)) {
    27. String msg = getInternal().getMessage("processPath");
    28.  
    29. log.error(msg + " " + request.getRequestURI());
    30. response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    31.  
    32. return null;
    33. }
    34.  
    35. path = path.substring(prefix.length());
    36.  
    37. int slash = path.lastIndexOf("/");
    38. int period = path.lastIndexOf(".");
    39.  
    40. if ((period >= 0) && (period > slash)) {
    41. path = path.substring(0, period);
    42. }
    43.  
    44. return (path);
    45. }
  3. processLocale() – фабричный метод получения локали обратившегося по http-пользователя:
     RequestProcessor.processLocale() [java]  ссылка
    1. protected void processLocale(HttpServletRequest request,
    2. HttpServletResponse response) {
    3. // Are we configured to select the Locale automatically?
    4. if (!moduleConfig.getControllerConfig().getLocale()) {
    5. return;
    6. }
    7.  
    8. // Has a Locale already been selected?
    9. HttpSession session = request.getSession();
    10.  
    11. if (session.getAttribute(Globals.LOCALE_KEY) != null) {
    12. return;
    13. }
    14.  
    15. // Use the Locale returned by the servlet container (if any)
    16. Locale locale = request.getLocale();
    17.  
    18. if (locale != null) {
    19. if (log.isDebugEnabled()) {
    20. log.debug(" Setting user locale '" + locale + "'");
    21. }
    22.  
    23. session.setAttribute(Globals.LOCALE_KEY, locale);
    24. }
    25. }
  4. processContent() – фабричный метод выставления Content-Type-а ответа сервера:
     RequestProcessor.processContent() [java]  ссылка
    1. protected void processContent(HttpServletRequest request,
    2. HttpServletResponse response) {
    3. String contentType =
    4. moduleConfig.getControllerConfig().getContentType();
    5.  
    6. if (contentType != null) {
    7. response.setContentType(contentType);
    8. }
    9. }
  5. processNoCache() – фабричный метод выставления заголовков запрета кеширования на стороне клиента (браузера):
     RequestProcessor.processNoCache() [java]  ссылка
    1. protected void processNoCache(HttpServletRequest request,
    2. HttpServletResponse response) {
    3. if (moduleConfig.getControllerConfig().getNocache()) {
    4. response.setHeader("Pragma", "No-cache");
    5. response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
    6. response.setDateHeader("Expires", 1);
    7. }
    8. }
  6. processPreprocess() – операция-зацепка для процедур общего назначения, потребность в которых может возникнуть именно в данном месте – после полной идентификации запроса и до запуска Action-а на выполнение:
     RequestProcessor.processPreprocess() [java]  ссылка
    1. protected boolean processPreprocess(HttpServletRequest request,
    2. HttpServletResponse response) {
    3. return (true);
    4. }

и т.д.

Дальше можно увидеть еще несколько зацепок: processRoles(), processValidate(), processForward() и processInclude(); и остальное все – фабричные методы: processMapping(), processActionForm(), processPopulate(), processActionCreate() и т.д. – все содержат продуманные реализации по-умалчанию, которые вполне подходят в большинстве случаев. Весь класс: RequestProcessor.

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

Шаблонные методы настолько фундаментальны, что встречаются почти в каждом абстрактном классе. Очень широко шаблонные методы используются в различных фреймворках, например, из самых известных это: Struts, Spring.

Родственные паттерны

Фабричные методы часто вызываются из шаблонных. В примере из раздела «Мотивация» шаблонный метод OpenDocument вызывал фабричный метод DoCreateDocument.
Стратегия: шаблонные методы применяют наследование для модификации части алгоритма. Стратегии используют делегирование для модификации алгоритма в целом.

Реализации:

java(1)   +добавить

1) RequestProcessor.java на java, code #528[автор:this]