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());

    // 5. В залежності від 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)
      ...
    }
  }
}

 


Споріднені теми