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


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

© Habrahabr.ru