Паттерн шаблонный метод (template method)

Template method представляет собой поведенческий шаблон проектирования. Вспоминать про него стоит всякий раз, когда в вашем проекте появляется дублирующийся код (именно с таким, плохим кодом он иногда может справиться).

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

Рассмотрим шаблон проектирования «template method» на примере шахматных фигур.

Пешки (Pawn), кони (Horse) и ладьи (Rook) будут наследоваться от класса Figure.

рис. 1 диаграмма классов шахматных фигур

рис. 1 диаграмма классов шахматных фигур

Каждая фигура умеет ходить, поэтому мы могли бы поместить в класс Figure чисто-виртуальный метод do_step()(потому что абстрактная фигура ходить не умеет) и переопределять его в каждой конкретной фигуре. Схематично, такой вариант показан на листинг 1.

void Pawn::do_step() {
  1 если можно ходить (наш ход) - переход на п.2;
    1.1 конец (ход не сделан);
  2 если конечная клетка (в которую идем) свободна - переход на п.3;
    2.1 если в конечной клетке стоит вражеская фигура - переход на п.2.2;
      2.1.1 конец (ход не сделан); // клетка занята своей фигурой
    2.2 убрать_вражескую фигуру(конечная клетка);
  3 если пешка может попасть в конечную клетку - переход на п.4.
    3.1 конец (ход не сделан);
  4 переместить фигуру в конечную клетку;
  5 если нам не стоит шах - переход на п.6;
    5.1 отмена всех изменений, сделанных за ход;
    5.2 конец (ход не сделан);
  6 конец (ход сделан).
}

void Rook::do_step() {
  1 если можно ходить (наш ход) - переход на п.2;
    1.1 конец (ход не сделан);
  2 если конечная клетка (в которую идем) свободна - переход на п.3;
    2.1 если в конечной клетке стоит вражеская фигура - переход на п.2.2;
      2.1.1 конец (ход не сделан); // клетка занята своей фигурой
    2.2 убрать_вражескую фигуру(конечная клетка);
  3 если ладья может попасть в конечную клетку - переход на п.4.
    3.1 конец (ход не сделан);
  4 переместить фигуру в конечную клетку;
  5 если нам не стоит шах - переход на п.6;
    5.1 отмена всех изменений, сделанных за ход;
    5.2 конец (ход не сделан);
  6 конец (ход сделан).
}

Аналогичным образом могли бы быть описаны все остальные фигуры. Мы видим, что большая часть кода дублируется. «Особыми» в каждом классе должны быть только п.3 и п.4 (для короля {при рокировке} или пешки {при достижении края доски или использовании права о проходе}).

Шаблон проектирования «Шаблонный метод» предлагает вынести весь алгоритм do_step в базовый класс, но специфичные для отдельных классов методы (такие как п.3 и п.4) сделать виртуальными. Классы наследники при этом будут описывать не весь алгоритм перемещения фигуры, а лишь те самые виртуальные методы.

рис. 2 шаблон проектирования template method

рис. 2 шаблон проектирования template method

void Figure::do_step() {
  1 если можно ходить (наш ход) - переход на п.2;
    1.1 конец (ход не сделан);
  2 если конечная клетка (в которую идем) свободна - переход на п.3;
    2.1 если в конечной клетке стоит вражеская фигура - переход на п.2.2;
      2.1.1 конец (ход не сделан); // клетка занята своей фигурой
    2.2 убрать_вражескую фигуру(конечная клетка);
  3 если check(конечная клетка) вернул false - переход на п.4;
    3.1 конец (ход не сделан);
  4 move(конечная клетка);
  5 если нам не стоит шах - переход на п.6;
    5.1 отмена всех изменений, сделанных за ход;
    5.2 конец (ход не сделан);
  6 конец (ход сделан).
}

bool Rook::ckeck(конечная клетка) {
  1 если конечная клетка расположена на одной строке или в одном столбце с фигурой - переход на п.2;
    1.1 выход (вернуть false);
  2 если между фигурой и конечной клеткой нет другой фигуры - переход на 3;
    2.1 выход (вернуть false);
  3 выход (вернуть true);
}

void Rook::move(конечная клетка) {
  1. переместить фигуру в конечную клетку;
  2. конец.
}

На листинг 2 схематично показана реализация шаблонного метода. Теперь классы наследники не должны описывать весь алгоритм хода фигуры. Сам же алгоритм, расположенный в базовом классе вызывает виртуальные методы наследников.

Замечания:

  1. для большинства фигур метод move() будет выглядеть одинаково (как в примере листинг 2). В этом случае, базовый класс может содержать такую реализацию в качестве стандартного поведения. При этом, переопределять метод должны лишь немногие классы с нестандартным поведением (в нашем примере, это фигуры Король и Пешка);
  2. шаблон проектирования «template method» тесно связан с идиомой NVI (Non-Virtual Interface) [2].

Полезная литература по теме статьи:

  1. Джейсон Мак-Колм Смит «Элементарные шаблоны проектирования» : Пер. с англ. — М. : ООО “И.Д. Вильямс”, 2013. — 304 с.;
  2. H. Satter «Virtuality» / C/C++ Users Journal, 19(9), September 2001.

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