
Chain of Responsibility を Go で
Chain of Responsibility は、 振る舞いに関するデザインパターンの一つで、 潜在的なハンドラーの連鎖の上を、 ハンドラーのどれかが処理するまで、 リクエストを回していきます。
このパターンを利用すると、 送り手のクラスと受け手の具象クラスとを結合することなく、 複数のオブジェクトにリクエストを処理する機会を与えることができます。 連鎖は実行時に、 標準のハンドラー・インターフェースに従うハンドラーから動的に構成されます。
概念的な例
Chain of Responsibility パターンを、 病院アプリを例に見ていきましょう。 病院には以下のような複数の場所や役割があります:
- 受付
- 医者
- 薬局
- 支払い
病人が到着すると、 まず受付に行き、 医者に会い、 薬局に行き、 そして支払い窓口などへ行きます。 患者は、 部局の連鎖の中を回され、 各部局はその機能を終えると、 患者を次の部局へ送ります。
このパターンは、 同一リクエストを処理する複数の候補がある時、 適用可能です。 リクエストを処理できる複数のオブジェクトがあり、 クライアントに受け手の選択をさせたくない場合も、 このパターンが役に立ちます。 もう一つの役に立つケースとしては、 クライアントを受け手から分離したい場合です。 クライアントは、 連鎖の最初の要素だけ知っておく必要があります。
病院の例のように、 患者は最初に受け付けに行きます。 その後、 患者の現在の状態に基づいて、 受け付けは連鎖内の次のハンドラーに送り出します。
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