Обзор

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

Пример из реального мира

Описание этого узора немного сложное, поэтому представим, что мы находимся в магазине, только что сделали покупки и подходим к кассовому аппарату. Кассир уже проверил наши покупки и ждет оплаты. У нас есть несколько видов оплаты на выбор:

  • ваучердо 20 долларов США.
  • наличными— до 100 долларов США
  • кредитная карта— до 200 долларов США

Пример кода

Давайте создадим первый класс, который будет отвечать за обработку платежей. Чтобы создать его, нам нужно будет предоставить какой-либо тип оплаты: карта, наличные или ваучер. Дополнительно класс реализует два дополнительных метода:

  • успехесли транзакция прошла успешно, возвращается сообщение в зависимости от того, чем мы заплатили
  • ошибкапоказывает информацию о том, что транзакция не удалась
class PaymentHandler
  attr_accessor :successor
  
  def initialize(successor = nil)
    @successor = successor
  end
  
  def call(price)
    successor ? successor.call(price) : error
  end
  
  private
  
  def success(message)
    message
  end

  def error
    "You are poor and have nothing to pay!"
  end
end

Первым способом оплаты мы попробуем использовать ваучер. Итак, давайте реализуем класс, который будет обрабатывать этот платеж. Ваучер можно использовать для оплаты покупок на сумму до 20 долларов США.

class VoucherPaymentHandler < PaymentHandler
  def call(price)
    price < 20 ? success('You paid by Voucher!') : super(price)
  end
end

Следующим шагом будет реализация обработчика cash.

class CashPaymentHandler < PaymentHandler
  def call(price)
    price < 100 ? success('You paid by Cash!') : super(price)
  end
end

И обработчик карты.

class CardPaymentHandler < PaymentHandler
  def call(price)
    price < 200 ? success('You paid by Card!') : super(price)
  end
end

На этом этапе мы можем реализовать нашу цепочку.

payment = VoucherPaymentHandler.new(CashPaymentHandler.new(CardPaymentHandler.new))

payment.call(10) => "You paid by Voucher!"
payment.call(50) => "You paid by Cash!"
payment.call(150) => "You paid by Card!"
payment.call(420) => "You are poor and have nothing to pay!"

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

Хорошо подготовленная цепочка позволяет обрабатывать бесконечное количество реализаций и размещать конкретный обработчик в любом месте цепочки.