Введение

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

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

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

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

Пример на ходу

package main

import "fmt"

// Request is the interface for a request.
type Request interface {
   getAmount() int
}

// Handler is the interface for handling a request.
type Handler interface {
   setNext(handler Handler) Handler
   handle(request Request) (bool, int)
}

// ATMWithdrawRequest is a request to withdraw money from an ATM.
type ATMWithdrawRequest struct {
   amount int
}

func (a *ATMWithdrawRequest) getAmount() int {
   return a.amount
}

// ATMWithdrawHandler is the concrete implementation of the Handler interface for an ATM withdraw request.
type ATMWithdrawHandler struct {
   next   Handler
   amount int
}

func (a *ATMWithdrawHandler) setNext(handler Handler) Handler {
   a.next = handler
   return handler
}

func (a *ATMWithdrawHandler) handle(request Request) (bool, int) {
   if request.getAmount() <= a.amount {
      fmt.Println("ATM withdraw request handled by ATMWithdrawHandler.")
      return true, request.getAmount()
   }
   fmt.Println("ATM withdraw request not handled by ATMWithdrawHandler. Passing to next handler.")
   if a.next != nil {
      return a.next.handle(request)
   }
   return false, 0
}

// BankTransferRequest is a request to transfer money between accounts.
type BankTransferRequest struct {
   amount int
}

func (b *BankTransferRequest) getAmount() int {
   return b.amount
}

// BankTransferHandler is the concrete implementation of the Handler interface for a bank transfer request.
type BankTransferHandler struct {
   next   Handler
   amount int
}

func (b *BankTransferHandler) setNext(handler Handler) Handler {
   b.next = handler
   return handler
}

func (b *BankTransferHandler) handle(request Request) (bool, int) {
   if request.getAmount() <= b.amount {
      fmt.Println("Bank transfer request handled by BankTransferHandler.")
      return true, request.getAmount()
   }
   fmt.Println("Bank transfer request not handled by BankTransferHandler. Passing to next handler.")
   if b.next != nil {
      return b.next.handle(request)
   }
   return false, 0
}

func main() {
   // Create the handlers.
   atmWithdrawHandler := &ATMWithdrawHandler{amount: 100}
   bankTransferHandler := &BankTransferHandler{amount: 200}

   // Link the handlers into a chain of responsibility.
   atmWithdrawHandler.setNext(bankTransferHandler)

   // Create some requests to handle.
   request0 := &ATMWithdrawRequest{amount: 50}
   request1 := &ATMWithdrawRequest{amount: 200}
   request2 := &BankTransferRequest{amount: 15000}

   // Handle the requests.
   result0, amount0 := atmWithdrawHandler.handle(request0)
   result1, amount1 := atmWithdrawHandler.handle(request1)
   result2, amount2 := atmWithdrawHandler.handle(request2)

   // Print the results.
   if result0 {
      fmt.Printf("Request 0 handled successfully. Amount: %d\n", amount0)
   } else {
      fmt.Println("Request 0 not handled.")
   }
   if result1 {
      fmt.Printf("Request 1 handled successfully. Amount: %d\n", amount1)
   } else {
      fmt.Println("Request 1 not handled.")
   }
   if result2 {
      fmt.Printf("Request 2 handled successfully. Amount: %d\n", amount2)
   } else {
      fmt.Println("Request 2 not handled.")
   }
}

Пошаговое объяснение реализации шаблона цепочки ответственности в предоставленном коде:

  1. Интерфейс Request определяет контракт для запроса. Он имеет единственный метод getAmount(), который возвращает сумму запроса.
  2. Интерфейс Handler определяет контракт для обработчика запросов. У него есть два метода: setNext() для установки следующего обработчика в цепочке и handle() для обработки запроса.
  3. Структура ATMWithdrawRequest реализует интерфейс Request для запроса на снятие средств через банкомат. Он имеет одно поле amount для хранения суммы запроса.
  4. Структура ATMWithdrawHandler реализует интерфейс Handler для запроса на снятие средств через банкомат. Он имеет два поля: next для хранения следующего обработчика в цепочке и amount для хранения максимальной суммы, которую может обработать этот обработчик.
  5. Метод setNext() структуры ATMWithdrawHandler устанавливает следующий обработчик в цепочке и возвращает обработчик.
  6. Метод handle() структуры ATMWithdrawHandler обрабатывает запрос. Если сумма запроса меньше или равна полю amount обработчика, он обрабатывает запрос и возвращает true и сумму запроса. В противном случае он передает запрос следующему обработчику в цепочке, если он существует, иначе возвращает false и 0.
  7. Структура BankTransferRequest реализует интерфейс Request для запроса банковского перевода. Он имеет одно поле amount для хранения суммы запроса.
  8. Структура BankTransferHandler реализует интерфейс Handler для запроса банковского перевода. Он имеет два поля: next для хранения следующего обработчика в цепочке и amount для хранения максимальной суммы, которую может обработать этот обработчик.
  9. Метод setNext() структуры BankTransferHandler устанавливает следующий обработчик в цепочке и возвращает обработчик.
  10. Метод handle() структуры BankTransferHandler обрабатывает запрос. Если сумма запроса меньше или равна полю amount обработчика, он обрабатывает запрос и возвращает true и сумму запроса. В противном случае он передает запрос следующему обработчику в цепочке, если он существует, иначе возвращает false и 0.
  11. В функции main() обработчики создаются и связываются в цепочку ответственности путем вызова setNext() для первого обработчика и передачи второго обработчика в качестве аргумента.
  12. Некоторые запросы создаются для обработки, в том числе ATMWithdrawRequest на сумму 50, ATMWithdrawRequest на сумму 200 и BankTransferRequest на сумму 15000.
  13. Запросы обрабатываются путем вызова метода handle() для первого обработчика в цепочке, который передает запрос вниз по цепочке до тех пор, пока он не будет обработан или не будет достигнут конец цепочки.
  14. Результаты каждого запроса выводятся на консоль, указывая, был ли запрос обработан успешно, и сумму запроса, если он был обработан.

Вот и все! Шаблон цепочки ответственности обеспечивает гибкий и масштабируемый способ обработки запросов, позволяя цепочке обработчиков обрабатывать запросы последовательно, пока один из них не обработает запрос или не будет достигнут конец цепочки.

Заключение

В заключение отметим, что паттерн «Цепочка ответственности» — отличное решение для ситуаций, когда запрос должен обрабатываться разными обработчиками в определенном порядке. Создавая цепочку обработчиков, каждый обработчик отвечает за проверку того, может ли он обработать запрос, и если он не может, он передает запрос следующему обработчику в цепочке. Этот шаблон проектирования обеспечивает большую гибкость и расширяемость, позволяя легко добавлять новые обработчики в цепочку, не затрагивая существующий код. Кроме того, он продвигает принцип единой ответственности, поскольку каждый обработчик имеет четкую и определенную роль в цепочке, что упрощает понимание, поддержку и тестирование кода.

Если вам нравится читать статьи на Medium и вы заинтересованы в том, чтобы стать участником, я буду рад поделиться с вами своей реферальной ссылкой!

https://medium.com/@adamszpilewicz/membership