Цепочка Обязанностей (Chain of responsibility) – поведенческий шаблон проектирования, который позволяет избежать жесткой привязки отправителя запроса к получателю, позволяя нескольким объектам обработать запрос. Все возможные обработчики запроса образуют цепочку, а сам запрос перемещается по этой цепочке, пока один из ее объектов не обработает запрос. Каждый объект при получении запроса выбирает, либо обработать запрос, либо передать выполнение запроса следующему по цепочке.
Когда применяется цепочка обязанностей?
- Когда имеется более одного объекта, который может обработать определенный запрос
- Когда надо передать запрос на выполнение одному из нескольких объект, точно не определяя, какому именно объекту
- Когда набор объектов задается динамически
Формальное определение на языке C#:
class Client
{
void Main()
{
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
h1.Successor = h2;
h1.HandleRequest(2);
}
}
abstract class Handler
{
public Handler Successor { get; set; }
public abstract void HandleRequest(int condition);
}
class ConcreteHandler1 : Handler
{
public override void HandleRequest(int condition)
{
if (condition == 1)
{
// обработка;
}
else if (Successor != null)
{
Successor.HandleRequest(condition);
}
}
}
class ConcreteHandler2 : Handler
{
public override void HandleRequest(int condition)
{
if (condition==2)
{
// обработка;
}
else if (Successor != null)
{
Successor.HandleRequest(condition);
}
}
}
Участники
- Handler: определяет интерфейс для обработки запроса. Также может определять ссылку на следующий обработчик запроса
- ConcreteHandler1 и ConcreteHandler2: конкретные обработчики, которые реализуют функционал для обработки запроса. При невозможности обработки и наличия ссылки на следующий обработчик, передают запрос этому обработчику
В данном случае для простоты примера в качестве параметра передается некоторое число, и в зависимости от значения данного числа обработчики и принимают решения об обработке запроса.
- Client: отправляет запрос объекту Handler
То есть у нас образуется небольшая цепочка обработки запроса:
Использование цепочки обязанностей дает нам следующие преимущества:
- Ослабление связанности между объектами. Отправителю и получателю запроса ничего не известно друг о друге. Клиенту неизветна цепочка объектов, какие именно объекты составляют ее, как запрос в ней передается.
- В цепочку с легкостью можно добавлять новые типы объектов, которые реализуют общий интерфейс.
В то же время у паттерна есть недостаток: никто не гарантирует, что запрос в конечном счете будет обработан. Если необходимого обработчика в цепочки не оказалось, то запрос просто выходит из цепочки и остается необработанным.
Использование паттерна довольно часто встречается в нашей жизни. Достаточно распространена ситуация, когда чиновники перекладывают друг на друга по цепочке выполнения какого-нибудь дела, и оно в конце концов оказывается не выполненным. Или когда мы идем в поликлинику, но при этом точно не знаем характер заболевания. В этом случае мы идем к терапевту, а он в зависимости от заболевания уже может либо сам лечить, либо отправить на лечение другим специализированным врачам.
Рассмотрим конкретный пример. Допустим, необходимо послать человеку определенную сумму денег. Однако мы точно не знаем, какой способ отправки может использоваться: банковский перевод, системы перевода типа WesternUnion и Unistream или система онлайн-платежей PayPal. Нам просто надо внести сумму, выбрать человека и нажать на кнопку. Подобная система может использоваться на сайтах фриланса, где все отношения между исполнителями и заказчиками происходят опосредованно через функции системы и где не надо знать точные данные получателя.
class Program
{
static void Main(string[] args)
{
Receiver receiver = new Receiver(false, true, true);
PaymentHandler bankPaymentHandler = new BankPaymentHandler();
PaymentHandler moneyPaymentHnadler = new MoneyPaymentHandler();
PaymentHandler paypalPaymentHandler = new PayPalPaymentHandler();
bankPaymentHandler.Successor = paypalPaymentHandler;
paypalPaymentHandler.Successor = moneyPaymentHnadler;
bankPaymentHandler.Handle(receiver);
Console.Read();
}
}
class Receiver
{
// банковские переводы
public bool BankTransfer { get; set; }
// денежные переводы - WesternUnion, Unistream
public bool MoneyTransfer { get; set; }
// перевод через PayPal
public bool PayPalTransfer { get; set; }
public Receiver(bool bt, bool mt, bool ppt)
{
BankTransfer = bt;
MoneyTransfer = mt;
PayPalTransfer = ppt;
}
}
abstract class PaymentHandler
{
public PaymentHandler Successor { get; set; }
public abstract void Handle(Receiver receiver);
}
class BankPaymentHandler : PaymentHandler
{
public override void Handle(Receiver receiver)
{
if (receiver.BankTransfer == true)
Console.WriteLine("Выполняем банковский перевод");
else if (Successor != null)
Successor.Handle(receiver);
}
}
class PayPalPaymentHandler : PaymentHandler
{
public override void Handle(Receiver receiver)
{
if (receiver.BankTransfer == true)
Console.WriteLine("Выполняем перевод через PayPal");
else if (Successor != null)
Successor.Handle(receiver);
}
}
// переводы с помощью системы денежных переводов
class MoneyPaymentHandler : PaymentHandler
{
public override void Handle(Receiver receiver)
{
if (receiver.MoneyTransfer == true)
Console.WriteLine("Выполняем перевод через системы денежных переводов");
else if (Successor != null)
Successor.Handle(receiver);
}
}
Класс Receiver с помощью конструктора и передаваемых в него параметров устанавливает возможные используемые системы платежей. При осуществлении платежа каждый отдельный объект PaymentHandler будет проверять установку у получателя определенного типа платежей. И если произойдет сопоставление типа платежей у получателя объекту PaymentHandler, то данный объект выполняет платеж. Если же необходимого способа платежей не будет определено, то деньги остаются в системе.
При этом преимуществом цепочки является и то, что она позволяет расположить последовательность объектов-обработчиков в ней в зависимости от их приоритета.
