Patterns. Паттерн Chain of Responsibility. Пример реализации на C#

Паттерн Chain of Responsibility (цепочка обязанностей). Пример реализации на C# для заданной структуры классов

Данная тема предназначена для изучения особенностей реализации паттерна Chain of Responsibility на примере некоторой упрощенной структуры классов. Перед изучением темы рекомендуется ознакомиться с особенностями паттерна, которые представлены в теме:

 


Содержание


Поиск на других ресурсах:

1. Условие задачи

Разработать приложение с использованием паттерна Chain of Responsibility на основе заданных условий.

Программа должна реализовывать выполнение запроса от клиента, который в зависимости от введенного значения кода операции (+, , *, /, %) должен получить соответствующий результат операции над двумя числами. Рассматриваются следующие операции:

  • сложение двух чисел, код операции + (плюс);
  • вычитание двух чисел, код операции (минус);
  • умножение двух чисел, код операции *;
  • деление двух чисел. Здесь необходимо реализовать деление нацело (код операции %) и обычное деление с учетом дробной части результата (код операции /).

 

2. Решение
2.1. Ввод метода обработки

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

2.2. Разработка структуры классов, соответствующих паттерну Chain of Responsibility

С учетом общей структуры паттерна Chain of Responsibility и ввода метода OperationRequest() разрабатывается структура классов, изображенная на рисунке 1. Дальнейшие шаги данной темы содержат описание работы разработанных классов.

Паттерн Chain of Responsibility. Реализация на C#. Структура классов

Рисунок 1. Структура классов, соответствующих условию задачи

 

2.3. Класс Operation

Класс Operation является базовым в иерархии классов. В этом классе создаются все необходимые средства для реализации паттерна Chain of Responsibility. В классе объявляются следующие элементы:

  • внутреннее поле successor, являющееся ссылкой на преемника. Эта ссылка обрабатывается в каждом классе-обработчике Add, Sub, Mult, DivInt, DivFloat. В классе Mult эта ссылка установлена ​​в значение null, поскольку этот класс является последним в цепочке обработчиков;
  • внутреннее поле numOp, определяющее операцию. Это поле имеет тип char и содержит обозначение операции (+, , *, /, %);
  • конструктор, получающий два параметра: ссылку на преемника и вид операции;
  • виртуальный метод OperationRequest(), непосредственно обрабатывающий запрос. Этот метод переопределяется одноименными методами в унаследованных классах;
  • метод DoRequest(), который по коду операции numOp определяет, нужно ли обрабатывать запрос. Если значение numOp равно ‘0’, то запрос не обрабатывается;
  • метод SetHandler() – реализует установку обработчика (successor) и операции обработки, закрепленных за ним (numOp).

После ввода класса, листинг программы следующий

using System;

namespace ConsoleApp20
{
  // Класс, который есть общим интерфейсом для классов-обработчиков
  class Operation
  {
    // 1. Внутренние переменные
    // 1.1. Ссылка на преемника
    private Operation successor;

    // 1.2. Идентификатор обработчика, обрабатывающего запрос
    private char numOp;

    // 2. Конструкторы
    public Operation(Operation _successor = null, char _numOp = '0')
    {
      successor = _successor;
      numOp = _numOp;
    }

    // 3. Метод, который обрабатывается по цепочке
    public virtual double OperationRequest(int num1, int num2)
    {
      if (successor != null)
        return successor.OperationRequest(num1, num2);
      else
        return 0.0;
    }

    // 4. Метод, который определяет, нужно ли обрабатывать запрос
    public bool DoRequest()
    {
      return numOp != '0';
    }

    // 5. Метод, устанавливающий обработчика и возможность обработки
    public void SetOperation(Operation _successor, char _numOp)
    {
      successor = _successor;
      numOp = _numOp;
    }
  }
}

На следующих шагах в пространство имен будут добавлены другие классы.

 

2.4. Класс-обработчик Add

В классе обработчике Add реализуется операция сложения двух чисел. Этот класс унаследован от базового класса Operation. Непосредственно добавление реализовано в переопределенном методе OperationRequest().

Текст класса следующий

using System;

namespace ConsoleApp20
{

  ...

  // Класс-обработчик, реализующий операцию сложения чисел.
  class Add : Operation
  {
    // Конструктор, вызывает конструктор базового класса
    public Add(Operation _operation, char _numOp)
        : base(_operation, _numOp)
    {
    }

    // Метод, суммирующий два числа
    public override double OperationRequest(int num1, int num2)
    {
      if (base.DoRequest())   // Если разрешена операция сложения,
        return num1 + num2; // то вернуть сумму чисел
      else // иначе передать обработку следующему в цепочке методу
        return base.OperationRequest(num1, num2);
    }
  }
}

 

2.5. Клас-обработчик Sub

Класс Sub, реализующий вычитание двух чисел, работает по тому же принципу что и класс Add (см. п. 2.4).

Ниже приведен листинг класса

...

// Класс-обработчик, реализующий операцию вычитания чисел.
class Sub : Operation
{
  // Конструктор, вызывает конструктор базового класса
  public Sub(Operation _operation, char _numOp)
      : base(_operation, _numOp)
  {
  }

  // Метод, реализующий вычитание чисел
  public override double OperationRequest(int num1, int num2)
  {
    if (base.DoRequest())   // Если разрешена операция вычитания,
      return num1 - num2; // то вернуть разницу чисел
    else // иначе передать обработку далее по цепочке
      return base.OperationRequest(num1, num2);
  }
}

...

 

2.6. Промежуточный класс Div

Класс Div является базовым классом для классов-обработчиков DivInt и DivFloat, реализующих деление двух чисел. В свою очередь, для обеспечения правильной организации работы паттерна Chain of Responsibility класс Div наследуется от класса Operation.

Конструктор класса Div перенаправляет создание экземпляра одного из унаследованных классов на конструктор класса Operation. В класс Div могут быть добавлены другие операции (методы) обработки чисел.

Листинг класса Div приведен ниже

...

// Базовый класс для классов DivInt и DivFloat.
// Эти классы реализуют операции целочисленного и нецелочисленного деления.
class Div : Operation
{
  // Конструктор класса
  public Div(Operation _parent, char _numOp) : base(_parent, _numOp)
  {
  }
}

...

 

2.7. Классы-обработчики DivInt и DivFloat

Классы-обработчики DivInt и DivFloat унаследованы от класса Div. При создании экземпляра конструкторы этих классов вызывают конструктор базового класса Div. Метод OperationMethod в этих классах-обработчиках работает по той же схеме, что и в классах Add и Sub.

Листинг классов выглядит следующим образом

...

// Класс - обработчик, который наследует класс Div.
// Класс реализует операцию деления нацело,
// при котором дробная часть результата отбрасывается.
class DivInt:Div
{
  // Конструктор - вызывает конструктор базового класса Div
  public DivInt(Operation _parent, char _numOp) : base(_parent, _numOp)
  { }

  // Конкретный метод обработки
  public override double OperationRequest(int num1, int num2)
  {
    if (base.DoRequest())         // если разрешена операция деления,
      return (int)(num1 / num2); // то вернуть целочисленное деление
    else // иначе перейти к следующему методу в цепочке
      return (int)base.OperationRequest(num1, num2);
  }
}

// Класс - обработчик, который наследует класс Div.
// Класс реализует операцию обычного деления,
// которая возвращает число с плавающей запятой.
class DivFloat : Div
{
  // Конструктор - вызывает конструктор базового класса Div
  public DivFloat(Operation _parent, char _numOp) : base(_parent, _numOp)
  { }

  // Конкретный метод обработки
  public override double OperationRequest(int num1, int num2)
  {
    if (base.DoRequest())     // если разрешена операция деления,
      return (double)num1 / num2; // то вернуть обычное деление
    else // иначе перейти к следующему методу в цепочке
      return base.OperationRequest(num1, num2);
  }
}

...

 

2.8. Класс-обработчик Mult. Последний обработчик в цепочке

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

Листинг класса Mult следующий

...

// Класс – обработчик, реализующий операцию умножения чисел.
// Это последний обработчик в цепочке методов,
// поэтому метод OperationReques() не похож на одноименные методы других обработчиков.
class Mult : Operation
{
  // Конструктор, вызывает конструктор базового класса.
  // Поскольку данный обработчик является последним в цепочке методов,
  // то ему не нужно передавать ссылку на предыдущего обработчика.
  public Mult(char _numOp)
      : base(null, _numOp)
  {
  }

  // Метод, умножающий два числа
  public override double OperationRequest(int num1, int num2)
  {
    if (base.DoRequest())   // Если разрешена операция умножения,
      return num1 * num2; // то вернуть произведение чисел
    else
    // иначе, вывести сообщение о том,
    // что в цепочке нет соответствующего обработчика
    {
      Console.WriteLine("There is no corresponding handler in the chain.");
      return 0.0;
    }
  }
}

...

 

2.9. Класс клиента Program. Функция main()

Клиентом в данном примере выступает функция main() из класса Program.

В функции main() выполняются следующие действия:

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

Текст класса Program и функции main() следующий

...

class Program
{
  static void Main(string[] args)
  {
    // Код клиента (Client)
    // 1. Создать цепочку классов-обработчиков
    //    Цепочка создается в порядке от последнего обработчика до первого
    // 1.1. Первым создается последний обработчик в цепочке
    Mult multOp = new Mult('*');

    // 1.2. Добавить обработчика DivInt
    // Обработчик получает ссылку на предварительно созданного обработчика multOp
    DivInt divIntOp = new DivInt(multOp, '%'); // % - указывает на целочисленное деление

    // 1.3. Добавить обработчика DivFloat
    DivFloat divFloatOp = new DivFloat(divIntOp, '/');

    // 1.4. Добавить следующего обработчика Sub.
    // Обработчик получает ссылку на предварительно созданного обработчика
    Sub subOp = new Sub(divFloatOp, '-');

    // 1.5. По тому же принципу добавить обработчика Add
    Add addOp = new Add(subOp, '+');

    // 2. Создать клиента.
    // Клиент указывает на цепочку объектов-обработчиков
    // client -> addOp -> subOp -> divFloatOp -> divIntOp -> multOp
    Operation client = new Operation(addOp);

    // 3. Задать операцию, которую нужно выполнить
    Console.Write("Operation = ");
    char op = Convert.ToChar(Console.ReadLine());

    // 4. Задать два числа, которые будут обрабатываться
    int num1, num2;
    Console.Write("num1 = ");
    num1 = Convert.ToInt32(Console.ReadLine());
    Console.Write("num2 = ");
    num2 = Convert.ToInt32(Console.ReadLine());

    // 4. В зависимости от op реализовать диспетчеризацию
    double res; // Результат вычисления некоторой операции

    switch(op)
    {
      case '+':
        // Установить обработчика addOp для операции '+'
        client.SetOperation(addOp, '+');

        // Вызвать метод обработки и вывести результат
        res = client.OperationRequest(num1, num2); // num1+num2
        Console.WriteLine("res = {0:f2}", res);
      break;
      case '-':
        // Установить обработчика subOp для операции '-'
        client.SetOperation(subOp, '-');

        // Вызвать метод обработки и вывести результат
        res = client.OperationRequest(num1, num2); // num1-num2
        Console.WriteLine("res = {0:f2}", res);
      break;
      case '%':
        // Установить обработчика divIntOp для операции '%'
        client.SetOperation(divIntOp, '%');

        // Вызвать метод обработки и вывести результат
        res = client.OperationRequest(num1, num2); // num1%num2
        Console.WriteLine("res = {0:f2}", res);
      break;
      case '/':
        // Установить обработчика divFloatOp для операции '/'
        client.SetOperation(divFloatOp, '/');

        // Вызвать метод обработки и вывести результат
        res = client.OperationRequest(num1, num2); // num1 / num2
        Console.WriteLine("res = {0:f2}", res);
      break;
      case '*':
        // Установить обработчика multOp для операции '*'
        client.SetOperation(multOp, '*');

        // Вызвать метод обработки и вывести результат
        res = client.OperationRequest(num1, num2);
        Console.WriteLine("res = {0:f2}", res);
      break;
      default:
        Console.WriteLine("Incorrect operation");
      break;
    }
  }
}

 

2.10. Сокращенный программный код

Используя комбинации клавиш Ctrl+C, Ctrl+V можно скомпоновать текст демонстрационной программы на основе текстов классов из пунктов 2.3-2.9. Программа реализована как приложение типа Console Application.

Сокращенный текст программы следующий

using System;

namespace ConsoleApp20
{
  // Класс, являющийся общим интерфейсом для классов-обработчиков
  class Operation
  {
    ...
  }

  // Класс-обработчик, реализующий операцию сложения чисел.
  class Add : Operation
  {
    ...
  }

  // Класс-обработчик, реализующий операцию вычитания чисел.
  class Sub : Operation
  {
    ...
  }

  // Класс, который является базовым для классов DivInt и DivFloat.
  // Эти классы реализуют операции целочисленного и нецелочисленного деления.
  class Div : Operation
  {
    ...
  }

  // Класс – обработчик, наследующий класс Div.
  class DivInt : Div
  {
    ...
  }

  // Класс – обработчик, который наследует класс Div.
  class DivFloat : Div
  {
    ...
  }

  // Класс – обработчик, реализующий операцию умножения чисел.
  class Mult : Operation
  {
    ...
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Код клиента (Client)
      ...
    }
  }
}

 


Связанные темы