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

от автора


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

Когда приходится спрашивать человека, какие паттерны проектирования ему приходилось использовать чаще всего, почему-то мало кто называет паттерн «Шаблонный метод» (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.

ссылка на оригинал статьи https://habrahabr.ru/post/277295/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *