Намір

Визначення скелету алгоритму у вигляді операцій, відокремлюючи деякі його кроки у дочірні класи. Метод Шаблону дозволяє дочірнім класам перевизначати певні кроки алгоритму без зміни структури алгоритму.

Мотивація

Розглянемо каркас програми, який забезпечує класи Application і Document. Клас Application відповідальний за відкривання існуючих документів, збережених у зовнішньому форматі, на подобі файлу. Об’єкт ADocument представляє інформацію у документі, як тільки він прочитаний з файлу. Програми побудовані за допомогою каркасу (framework) можуть створювати дочірні класи Application і Document для вдоволення своїх потреб. Наприклад, програма для малювання визначає дочірні класи DrawApplication i DrawDocument; програма для таблиць визначає дочірні класи SpreadsheetApplication i SpreadsheetDocument. Абстрактний клас Application визначає алгоритм для відкривання і зчитування документу за допомогою опреації OpenDocument:
void Application :: OpenDocument (const char* name) 
{
	if (!CanOpenDocument(name)) 
	{
		// cannot handle this document
		return;
	}

	Document* doc = DoCreateDocument () ;

	if (doc) 
	{
		_docs->AddDocument (doc) ;
		AboutToOpenDocument (doc) ;
		doc->Open () ;
		doc->DoRead () ;
	}
}
Метод OpenDocument визначає кожен крок для відкриття документу. Він перевіряє, чи документ може бути відкритим, створює програмно-залежний об’єкт Document, додає його до свого набору документів і зчитує документ з файлу. Ми називаємо OpenDocument методом шаблону. Метод шаблону визначає алгоритм у термінах абстрактних операцій, які перевизначають потомки для забезпечення конкретної поведінки. Потомки класу Application визначають кроки алгоритму, які перевіряють чи документ може бути відкритим (CanOpenDocument) з створеним об’єктом Document (DoCreateDocument). Класи Document визначає крок, який зчитує документ (DoRead). Метод шаблону також визначає операцію, яка дозволяє потомкам Application знати, коли документ відкривається (AboutToOpenDocument), на випадок, якщо їм це необхідно. Визначаючи деякі з кроків алгоритму, використовуючи абстрактні операції, метод шаблону змінює їхній порядок, але він дозволяє потомкам Application i Document змінювати дані кроки для задоволення їхніх потреб.

Застосовуваність

Шаблон проектування Метод Шаблону повинен бути використаний коли:
  • для одинарної реалізації незмінних частин алгоритму і утримування їх у потомках для реалізації поведінки, яка може варіюватися.
  • Коли загальна поведінка між потомками повинна бути структурована і локалізована у загальних класах для запобігання дублювання коду. Дана стратегія являється хорошим прикладом “рефакторингу для узагальнювання”, як описували Опдюк і Джонсон (Opdyke, Johnson) [OJ93]. Ви спочатку визначаєте різницю у існуючому коді, після чого відокремлюєте дану різницю у нові операції. На кінець, ви замінюєте код, що відрізняється методом шаблону, який викликає один з даних нових операцій.
  • Для контролювання розширення потомків. Ви можете визначити метод шаблону, який викликає операцїі “hook” (перегляньте секцію Наслідки), у специфічних точках, тобто дозволяючи розширенняч тільки в даних точках коду.

Структура

Учасники

  • AbstractClass (Application)
    • визначає абстрактні примітивні операції, які визначають конкретні потомки для реалізації кроків алгоритму.
    • Реалізовує метод шаблону, визначаючи скелет алгоритму. Метод шаблону викликає примітивні операції разом з операціями визначиними у AbstractClass або з інших об’єктів.
  • ConcreteClass (MyApplication)
    • реалізовує примітивні операції для виконування специфічних для класу кроків алгоритму.

Співпрацювання

  • ConcreteClass опирається на AbstractClass для реалізації незмінних кроків алгоритму.

Наслідки

Метод шаблону являється фундаментальною технікою для повторного використання коду. Він є особливо важливим у бібліотеках класів, тому що являється самою сутнсітю рефакторингу загальної розповсюдженої поведінки у класах бібліотеки. Методи шаблону спричиняють зворотньо-контрольовану структуру, яка інколи називається “принцип Голівуду”, тобто “Не викликайте нас, ми викличем вас самі.” [Swe85]. Це спричиняє викликання батьківським класом операцій потомків, а не навпаки. Метод шаблону викликає наступні типи операцій:
  • конкретні операції (або на ConcreteClass, або на класах клієнта);
  • конкретні операції AbstractClass (тобто операції, які в загальному корисні для потомків);
  • примітивні операції (тобто абстрактні операції);
  • методи фабрики (даний шаблон проектування); і
  • hook-виклики, які забезпечують поведінку за умовчанням, щоб потомки могли за необхідності розширюватися. Hook-виклики часто за умовчанням нічого не виконують.
Важливо вказувати для методу шаблона, які операції являються hook-операціями (і можуть бути перевантаженими) і які являються абстрактними операціями (повинні бути перевантаженими). Для ефективного багаторазового використання абстрактного класу автори потомків повинні розуміти, які операції спроектовані для перевантаження. Виклик потомку може розширювати поведінку операції батьківського класу за допомогою перевантаження операції і безпосереднього виклику її з батьківського класу:
void DerivedClass :: Operation () 
{
	// DerivedClass extended behavior
	ParentClass :: Operation () ;
}
Нажаль, легко призабути викликати успадковану операцію. Ми можемо перетворювати такі опреації у метод шаблону для надання батьківському класу контроль над тим, як потомок розширює його. Ідея полягає у викликанні hook-операції з методу шаблону у її батьківському класі. Потомки можуть перевизначати дану hook-операцію:
void ParentClass :: Operation () 
{
	// ParentClass behavior
	HookOperation () ;
}
Метод HookOperation не виконує роботи у батьківському класі ParentClass:
void ParentClass :: HookOperation () { }
Потомки перевизначають HookOperation для розширення її поведінки:
void DerivedClass :: HookOperation () 
{
	// derived class extension
}

Реалізація

Три реалізаційні питання, які вартують уваги:
  1. Використання контролю доступу C++. У С++, примітивні операції, які викликає метод шаблону може бути оголошений у якості захищеного методу. Це запевняє, що вони викликаються тільки методом шаблону. Примітивні операції, які повинні бути перевантаженими оголошуються у якості чистих віртуальних методів. Метод шаблону не повинен бути перевантаженим; отже ви можете оголошувати метод шаблону не віртуальним методом.
  2. Мінімізації примітивних операцій. Важлива ціль у розробці методу шаблону полягає у мінімізації кількості примітивних операцій, які потомки повинні перевизначати для конкретизації алгоритму. Чим більше операції, що потребують перевизначення, тим більш рутинною стає робота клієнтів.
  3. Узгодження іменування. Ви можете ідентифікувати операції, які повинні бути перевизначеними, додаючи префікс до їхніх імен. Наприклад, каркас MacApp для програм Macintosh [App89] додає префікси до методів шаблону “Do-”: “DoCreateDocument”, “DoRead” і так далі.

Приклад коду

Наступний C++ код демонструє, як батьківський клас може надавати незмінний код для його потомків. Приклад взятий з NeXT`AppKit [Add94]. Розглянемо клас View, який підтримує малювання на екрані. Клас View змушує виконувати правило, за яким його потомки можуть малювати у робоче поле тільки після того, як він стане у “фокусі”, що вимагає певного правильно встановленого стану малювання (наприклад, колір і шрифти). Ми можемо використовувати метод шаблону Display для встановлення даного стану. View визначає дві конкретні операції, SetFocus і ResetFocus, які встановлюють і очищують стан малювання, відповідно. Операція класу DoDisplayhook фактично виконує малювання. Display викликає SetFocus перед DoDisplay для встановлення стану малювання; Display викликає ResetFocus для звільнення стану малювання.
void View :: Display () 
{
	SetFocus () ;
	DoDisplay () ;
	ResetFocus () ;
}
Для підтримки правила інваріанти, клієнти View завжди викликають Display, і потомки View завжди перевизначають DoDisplay. Метод DoDisplay порожній у класі View:
void View :: DoDisplay () { }
Потомки перевизначають його для додавання їхньої специфічної поведінки малювання:
void MyView :: DoDisplay () 
{
	// відмалювати вміст View
}

Відомі використання

Метод шаблону являється настільки фундаментальним, що вони можуть бути знайденими майже у кожному абстрактному класі. Вірфс-Брок [WBWW90, WBJ90] містить хороший перегляд і обговорення методів шаблону.

Споріднені шаблони

Методи Фабрики часто викликаються методом шаблону. У прикладі Мотивації, метод фабрики DoCreateDocument викликається методом шаблону OpenDocument. Стратегія: методи шаблону використовують успадкованість для варіювання частин алгоритму. Стратегії використовують делегування для варіювання цілого алгоритму.