Введение
Шаблон цепочки ответственности — это поведенческий шаблон проектирования, который позволяет группе объектов обрабатывать запрос, где каждый объект может либо обработать запрос, либо передать его следующему объекту в цепочке. Объекты в цепочке часто называют обработчиками или процессорами.
Этот шаблон полезен, когда есть несколько объектов, которые могут обработать запрос, но заранее неизвестно, какой объект будет обрабатывать запрос. Вместо жесткого кодирования определенного обработчика в клиентском коде шаблон цепочки ответственности позволяет добавлять или удалять обработчики во время выполнения, и клиентскому коду не нужно знать, какие обработчики находятся в цепочке.
Одним из распространенных примеров шаблона цепочки ответственности являются системы обработки событий, где несколько объектов могут обрабатывать событие, но заранее неизвестно, какой объект будет обрабатывать событие. Другой пример — веб-фреймворки, где функции промежуточного ПО могут обрабатывать запросы и передавать их следующей функции промежуточного ПО в цепочке.
В следующей статье мы более подробно рассмотрим шаблон цепочки ответственности и предоставим простой пример на 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.") } }
Пошаговое объяснение реализации шаблона цепочки ответственности в предоставленном коде:
- Интерфейс
Request
определяет контракт для запроса. Он имеет единственный методgetAmount()
, который возвращает сумму запроса. - Интерфейс
Handler
определяет контракт для обработчика запросов. У него есть два метода:setNext()
для установки следующего обработчика в цепочке иhandle()
для обработки запроса. - Структура
ATMWithdrawRequest
реализует интерфейсRequest
для запроса на снятие средств через банкомат. Он имеет одно полеamount
для хранения суммы запроса. - Структура
ATMWithdrawHandler
реализует интерфейсHandler
для запроса на снятие средств через банкомат. Он имеет два поля:next
для хранения следующего обработчика в цепочке иamount
для хранения максимальной суммы, которую может обработать этот обработчик. - Метод
setNext()
структурыATMWithdrawHandler
устанавливает следующий обработчик в цепочке и возвращает обработчик. - Метод
handle()
структурыATMWithdrawHandler
обрабатывает запрос. Если сумма запроса меньше или равна полюamount
обработчика, он обрабатывает запрос и возвращаетtrue
и сумму запроса. В противном случае он передает запрос следующему обработчику в цепочке, если он существует, иначе возвращаетfalse
и 0. - Структура
BankTransferRequest
реализует интерфейсRequest
для запроса банковского перевода. Он имеет одно полеamount
для хранения суммы запроса. - Структура
BankTransferHandler
реализует интерфейсHandler
для запроса банковского перевода. Он имеет два поля:next
для хранения следующего обработчика в цепочке иamount
для хранения максимальной суммы, которую может обработать этот обработчик. - Метод
setNext()
структурыBankTransferHandler
устанавливает следующий обработчик в цепочке и возвращает обработчик. - Метод
handle()
структурыBankTransferHandler
обрабатывает запрос. Если сумма запроса меньше или равна полюamount
обработчика, он обрабатывает запрос и возвращаетtrue
и сумму запроса. В противном случае он передает запрос следующему обработчику в цепочке, если он существует, иначе возвращаетfalse
и 0. - В функции
main()
обработчики создаются и связываются в цепочку ответственности путем вызоваsetNext()
для первого обработчика и передачи второго обработчика в качестве аргумента. - Некоторые запросы создаются для обработки, в том числе
ATMWithdrawRequest
на сумму 50,ATMWithdrawRequest
на сумму 200 иBankTransferRequest
на сумму 15000. - Запросы обрабатываются путем вызова метода
handle()
для первого обработчика в цепочке, который передает запрос вниз по цепочке до тех пор, пока он не будет обработан или не будет достигнут конец цепочки. - Результаты каждого запроса выводятся на консоль, указывая, был ли запрос обработан успешно, и сумму запроса, если он был обработан.
Вот и все! Шаблон цепочки ответственности обеспечивает гибкий и масштабируемый способ обработки запросов, позволяя цепочке обработчиков обрабатывать запросы последовательно, пока один из них не обработает запрос или не будет достигнут конец цепочки.
Заключение
В заключение отметим, что паттерн «Цепочка ответственности» — отличное решение для ситуаций, когда запрос должен обрабатываться разными обработчиками в определенном порядке. Создавая цепочку обработчиков, каждый обработчик отвечает за проверку того, может ли он обработать запрос, и если он не может, он передает запрос следующему обработчику в цепочке. Этот шаблон проектирования обеспечивает большую гибкость и расширяемость, позволяя легко добавлять новые обработчики в цепочку, не затрагивая существующий код. Кроме того, он продвигает принцип единой ответственности, поскольку каждый обработчик имеет четкую и определенную роль в цепочке, что упрощает понимание, поддержку и тестирование кода.
Если вам нравится читать статьи на Medium и вы заинтересованы в том, чтобы стать участником, я буду рад поделиться с вами своей реферальной ссылкой!