Шаблонний метод

Шаблонний метод

Коли доводиться питати людини, які патерни проектування йому доводилося використовувати, чомусь мало хто називає патерн «Шаблонний метод» (Template Method). Ймовірно, це пов'язано з пропуском в знанні номенклатури патернів, бо особисто я насилу уявляю собі, щоб більш-менш досвідчений програміст ні разу не використовував такий зручний і корисний патерн. Пропоную ще раз поглянути на нього ближче.

Отже, шаблонний метод. Ніякого відношення до шаблонів c++ він не має. Даний патерн примітний тим, що він дуже простий, інтуїтивно зрозумілий, і вкрай корисний. Відноситься він до категорії патернів поведінки і служить однієї простої мети — переопределению кроків деякого алгоритму в сімействі класів, похідних від базового, що визначає структуру цього самого алгоритму.

Припустимо, що ми пишемо клас Crypt, який призначений для шифрування деякою рядки тексту. У класі визначена функція шифрування:
void encrypt() {
// Встановлення початкових параметрів
setupRnd();
setupAlgorithm();

// Отримуємо рядок
std::string fContent = getString();
// Застосовуємо шифрування
std::string enc = applyEncryption(fContent);
// Зберігаємо рядок
saveString(fContent);

// Підчищаємо сліди роботи алгоритму
wipeSpace();
}

З допомогою патерну «Шаблонний метод» ми можемо використовувати алгоритм, представлений у функції encrypt(), щоб працювати з рядками, отриманими з різних джерел — з клавіатури, прочитані з диска, отримані з мережі. При цьому сама структура алгоритму і незмінні кроки (встановлення початкових параметрів, слідів підчищення роботи, і при бажанні застосування шифрування) залишаються незмінними. Це дозволяє нам:
  1. Повторно використовувати код, який не змінюється для різних підкласів;
  2. Визначити загальну поведінку сімейства підкласів, використовуючи один раз певний код;
  3. Розмежувати права доступу — при реалізації змінюваних кроків алгоритму ми будемо використовувати закриті віртуальні функції. Це гарантує, що такі операції будуть викликатися тільки в якості кроків модифікується алгоритму (або, швидше, не будуть викликатися похідними класами в невідповідних для цього місцях).
Отже, доповнимо клас Crypt необхідними членами:
private:
void setupRnd() {
// Якась ініціалізація алгоритму випадкових чисел
std::cout << "setup rnd\n";
};
void setupAlgorithm() {
// Початкові установки алгоритму шифрування
std::cout << "setup algorithm\n";
};
void wipeSpace() {
// Видалення слідів роботи
std::cout << "wipe\n";
};

virtual std::string applyEncryption(const std::string& content) {
// Шифрування
std::string result = someStrongEncryption(content);
return result;
}
virtual std::string getString() = 0;
virtual void saveString(const std::string& content) = 0;

Зверніть увагу, що функції закриті. Це свідоме обмеження на їх виклик, який не заважає перевизначити їх у похідному класі.
І, власне, похідний клас — шифрувальний файл на диску:
class DiskFileCrypt : public Crypt {
public:
DiskFileCrypt(const std::string& fName)
: fileName(fName) {};
private:
std::string fileName;

virtual std::string getString() {
std::cout << "get disk file named \"" << fileName << "\"\n";
// Прочитати файл з диска і повернути вміст
return fileContent;
}
virtual void saveString(const std::string& content) {
std::cout << "save disk file named \"" << fileName << "\"\n";
// Записати файл на диск
}
};

Вже зрозуміло, що при виклику
DiskFileCrypt d("foo.txt");
d.encrypt();

Буде виконаний алгоритм функції encrypt() і в консолі буде наступне:
setup rnd
 
setup algorithm
 
get disk file named "foo.txt"
 
save disk file named "foo.txt"
 
wipe
 

Механізм віртуальних функцій в прикладі вище служить виключно для кастомізації поведінки класів. Визначаючи їх як чисто віртуальні можна ставити вимоги до які наслідують класів. Наприклад, якщо б ми хотіли, щоб спадкоємці визначали алгоритм шифрування, слід було б зробити чисто віртуальної функцію-член класу applyEncryption.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.