Шаблонный метод (Template Method)

Шаблонный метод (Template Method) определяет общий алгоритм поведения подклассов, позволяя им переопределить отдельные шаги этого алгоритма без изменения его структуры.

Когда использовать шаблонный метод?

  • Когда планируется, что в будущем подклассы должны будут переопределять различные этапы алгоритма без изменения его структуры
  • Когда в классах, реализующим схожий алгоритм, происходит дублирование кода. Вынесение общего кода в шаблонный метод уменьшит его дублирование в подклассах.

Схематично в UML алгоритм можно изобразить следующим образом:

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

Формальная реализация паттерна на C#:

abstract class AbstractClass
{
    public void TemplateMethod()
    {
        Operation1();
        Operation2();
    }
    public abstract void Operation1();
    public abstract void Operation2();
}
 
class ConcreteClass : AbstractClass
{
    public override void Operation1()
    {
    }
 
    public override void Operation2()
    {
    }
}

Участники

  • AbstractClass: определяет шаблонный метод TemplateMethod(), который реализует алгоритм. Алгоритм может состоять из последовательности вызовов других методов, часть из которых может быть абстрактными и должны быть переопределены в классах-наследниках. При этом сам метод TemplateMethod(), представляющий структуру алгоритма, переопределяться не должен.
  • ConcreteClass: подкласс, который может переопределять различные методы родительского класса.

Рассмотрим применение на конкретном примере. Допустим, в нашей программе используются объекты, представляющие учебу в школе и в вузе:

class School
{
    // идем в первый класс
    public void Enter() { }
    // обучение
    public void Study() { }
    // сдаем выпускные экзамены
    public void PassExams() { }
    // получение аттестата об окончании
    public void GetAttestat() { }
}
 
class University
{
    // поступление в университет
    public void Enter() { }
    // обучение
    public void Study() { }
    // проходим практику
    public void Practice() { }
    // сдаем выпускные экзамены
    public void PassExams() { }
    // получение диплома
     public void GetDiploma() { }
}

Как видно, эти классы очень похожи, и самое главное, реализуют примерно общий алгоритм. Да, где-то будет отличаться реализация методов, где-то чуть больше методов, но в целом мы имеем общий алгоритм, а функциональность обоих классов по большому счету дублируется. Поэтому для улучшения структуры классов мы могли бы применить шаблонный метод:

class Program
{
    static void Main(string[] args)
    {
        School school = new School();
        University university = new University();
 
        school.Learn();
        university.Learn();
 
        Console.Read();
    }
}
abstract class Education
{
    public void Learn()
    {
        Enter();
        Study();
        PassExams();
        GetDocument();
    }
    public abstract void Enter();
    public abstract void Study();
    public virtual void PassExams()
    {
        Console.WriteLine("Сдаем выпускные экзамены");
    }
    public abstract void GetDocument();
}
     
class School : Education
{
    public override void Enter()
    {
        Console.WriteLine("Идем в первый класс");
    }
 
    public override void Study()
    {
        Console.WriteLine("Посещаем уроки, делаем домашние задания");
    }
 
    public override void GetDocument()
    {
        Console.WriteLine("Получаем аттестат о среднем образовании");
    }
}
 
class University : Education
{
    public override void Enter()
    {
        Console.WriteLine("Сдаем вступительные экзамены и поступаем в ВУЗ");
    }
 
    public override void Study()
    {
        Console.WriteLine("Посещаем лекции");
        Console.WriteLine("Проходим практику");
    }
 
    public override void PassExams()
    {
        Console.WriteLine("Сдаем экзамен по специальности");
    }
 
    public override void GetDocument()
    {
        Console.WriteLine("Получаем диплом о высшем образовании");
    }
}

При этом в базовом классе необязательно определять все методы алгоритма как абстрактные. Можно определить реализацию методов по умолчанию, как в случае с методом PassExams().

И в результате работы программы консоль выведет:

Идем в первый класс
Посещаем уроки, делаем домашние задания
Сдаем выпускные экзамены
Получаем аттестат о среднем образовании
Сдаем вступительные экзамены и поступаем в ВУЗ
Посещаем лекции
Проходим практику
Сдаем экзамен по специальности
Получаем диплом о высшем образовании

В данном случае надо отметить, что несмотря на то, что мы не можем в производном классе переопределить шаблонный метод Learn(), тем не менее мы можем скрыть реализацию этого метода в классе-наследнике:

class School : Education
{
    public new void Learn()
    {
    Console.WriteLine("Не хочу учиться");
    }
    //..........................................
}

В итоге реализация шаблонного метода не будет иметь смысла.

Также надо отметить ситуацию с наследованием базового класса. Например, у нас может быть ситуация, когда базовый класс Education сам наследует метод Learn от другого класса:

abstract class Learning
{
    public abstract void Learn();
}
abstract class Education :Learning
{
    public sealed override void Learn()
    {
        Enter();
        Study();
        PassExams();
        GetDocument();
    }
    //  остальные методы класса
}

Чтобы сокрыть алгоритм от изменения в классах наследниках, метод Learn объявляется с ключевым словом sealed.

Пример применения: настройте общую «боевую» подпрограмму с различными функциями ловушек для обработки каждого шага, например. уменьшать боеприпасы, рассчитывать вероятность попадания, разрешать попадания или пропуски, рассчитывать урон, и каждый тип навыка атаки будет реализовывать методы по-своему.

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

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