Цепочка обязанностей на Go
Цепочка обязанностей — это поведенческий паттерн, позволяющий передавать запрос по цепочке потенциальных обработчиков, пока один из них не обработает запрос.
Избавляет от жёсткой привязки отправителя запроса к его получателю, позволяя выстраивать цепь из различных обработчиков динамически.
Концептуальный пример
Давайте рассмотрим паттерн Цепочка обязанностей на примере приложения больницы. Госпиталь может иметь разные помещения, например:
- Приемное отделение
- Доктор
- Комната медикаментов
- Кассир
Когда пациент прибывает в больницу, первым делом он попадает в Приемное отделение, оттуда – к Доктору, затем в Комнату медикаментов, после этого – к Кассиру, и так далее. Пациент проходит по цепочке помещений, в которой каждое отправляет его по ней дальше сразу после выполнения своей функции.
Этот паттерн можно применять в случаях, когда для выполнения одного запроса есть несколько кандидатов, и когда вы не хотите, чтобы клиент сам выбирал исполнителя. Важно знать, что клиента необходимо оградить от исполнителей, ему необходимо знать лишь о существовании первого звена цепи.
Используя пример больницы, пациент сперва попадает в Приемное отделение. Затем, зависимо от его состояния, Приемное отделение отправляет его к следующему исполнителю в цепи.
department.go: Интерфейс обработчика
package main type Department interface { execute(*Patient) setNext(Department) } reception.go: Конкретный обработчик
package main import "fmt" type Reception struct { next Department } func (r *Reception) execute(p *Patient) { if p.registrationDone { fmt.Println("Patient registration already done") r.next.execute(p) return } fmt.Println("Reception registering patient") p.registrationDone = true r.next.execute(p) } func (r *Reception) setNext(next Department) { r.next = next } doctor.go: Конкретный обработчик
package main import "fmt" type Doctor struct { next Department } func (d *Doctor) execute(p *Patient) { if p.doctorCheckUpDone { fmt.Println("Doctor checkup already done") d.next.execute(p) return } fmt.Println("Doctor checking patient") p.doctorCheckUpDone = true d.next.execute(p) } func (d *Doctor) setNext(next Department) { d.next = next } medical.go: Конкретный обработчик
package main import "fmt" type Medical struct { next Department } func (m *Medical) execute(p *Patient) { if p.medicineDone { fmt.Println("Medicine already given to patient") m.next.execute(p) return } fmt.Println("Medical giving medicine to patient") p.medicineDone = true m.next.execute(p) } func (m *Medical) setNext(next Department) { m.next = next } cashier.go: Конкретный обработчик
package main import "fmt" type Cashier struct { next Department } func (c *Cashier) execute(p *Patient) { if p.paymentDone { fmt.Println("Payment Done") } fmt.Println("Cashier getting money from patient patient") } func (c *Cashier) setNext(next Department) { c.next = next } patient.go
package main type Patient struct { name string registrationDone bool doctorCheckUpDone bool medicineDone bool paymentDone bool } main.go: Клиентский код
package main func main() { cashier := &Cashier{} //Set next for medical department medical := &Medical{} medical.setNext(cashier) //Set next for doctor department doctor := &Doctor{} doctor.setNext(medical) //Set next for reception department reception := &Reception{} reception.setNext(doctor) patient := &Patient{name: "abc"} //Patient visiting reception.execute(patient) } output.txt: Результат выполнения
Reception registering patient Doctor checking patient Medical giving medicine to patient Cashier getting money from patient patient