
Введение
Шаблон цепочки ответственности — это поведенческий шаблон проектирования, который позволяет группе объектов обрабатывать запрос, где каждый объект может либо обработать запрос, либо передать его следующему объекту в цепочке. Объекты в цепочке часто называют обработчиками или процессорами.
Этот шаблон полезен, когда есть несколько объектов, которые могут обработать запрос, но заранее неизвестно, какой объект будет обрабатывать запрос. Вместо жесткого кодирования определенного обработчика в клиентском коде шаблон цепочки ответственности позволяет добавлять или удалять обработчики во время выполнения, и клиентскому коду не нужно знать, какие обработчики находятся в цепочке.
Одним из распространенных примеров шаблона цепочки ответственности являются системы обработки событий, где несколько объектов могут обрабатывать событие, но заранее неизвестно, какой объект будет обрабатывать событие. Другой пример — веб-фреймворки, где функции промежуточного ПО могут обрабатывать запросы и передавать их следующей функции промежуточного ПО в цепочке.
В следующей статье мы более подробно рассмотрим шаблон цепочки ответственности и предоставим простой пример на 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 и вы заинтересованы в том, чтобы стать участником, я буду рад поделиться с вами своей реферальной ссылкой!