Chain of Responsibilityとは
振る舞いに関するデザインパターンの一つ。複数のオブジェクト(ハンドラー)を鎖状につなぎ、それぞれのオブジェクトに対して下記を判断させることができるようになる。
- リクエストを処理すべきか。
- 連鎖に沿ってリクエストを渡すべきか。
長所
- リクエスト処理の順序をハンドラーの順序によって制御できる。
- 処理を起動するクラスと実際に処理をするクラスを分離できる。(単一責任の原則)
- 新規ハンドラーの追加の際にクライアント側の修正が必要ない。(開放閉鎖の原則)
短所
- 一部のリクエストが連鎖の最後まで到達しない可能性がある。同様にまったく処理されないままリクエストが連鎖の最後に到達する可能性がある。
(各ハンドラーは自分が処理必要のあるリクエストに対してのみ処理を行うため) - 処理のたらい回しを行うため、ピンポイントで必要な処理を行う場合よりも処理速度が劣る。
利用場面
- 多種のリクエストを様々な方法で処理しなければならないが、正確にどのようなリクエストがどのような順序で送られてくるのか予想ができない場合。
- 特定の順序で処理を行いたい場合。
- 処理の順序を動的に変更したい場合。
クラス図

- Handler
具象ハンドラーに共通したインターフェースを宣言する。基本的にはリクエストを処理するメソッドと、処理を次のハンドラーに回すためのメソッドを持つ。 - Base Handler
無い場合とある場合がある。具象ハンドラー内の定型コードがある場合、ここに抽出することでコードの重複を排除できる。
リクエストを受け取り、リクエストの処理の有無や次の連鎖にリクエストを渡すかどうかを判断する。 - Concrete Handler
リクエストを処理するためのメソッドを実装する。
実装例
Handler
package main
type Handler interface {
Validate(*InputData) bool
SetNext(Handler) Handler
}
Base Handler
package main
type IValidator interface {
IsValid(d *InputData) bool
Done()
Fail()
}
type Validator struct {
validator IValidator
next Handler
}
func NewValidator(v IValidator) *Validator {
return &Validator{validator: v}
}
// テンプレートメソッド
func (v *Validator) Validate(d *InputData) bool {
isValid := v.validator.IsValid(d)
if isValid {
v.validator.Done()
} else {
v.validator.Fail()
}
// バリデーションチェックの結果に関わらず次のハンドラーのバリデーション処理を呼び出している。
// 一つでも失敗した時点でチェックをやめることも可能。
if v.next != nil {
return v.next.Validate(d) && isValid
} else {
return isValid
}
}
func (v *Validator) SetNext(next Handler) Handler {
v.next = next
// 引数で受け取ったhandlerを返せば、handler1.SetNext(handler2).SetNext(handler3)のように連鎖できる。
return next
}
Concrete Handler
package main
import "fmt"
type IdValidator struct {}
func NewIdValidator() *IdValidator {
return &IdValidator{}
}
func (v *IdValidator) IsValid(d *InputData) bool {
return d.Id > 100
}
func (v *IdValidator) Done() {
fmt.Println("'ID' is OK.")
}
func (v *IdValidator) Fail() {
fmt.Println("'ID' is NG.")
}
package main
import (
"fmt"
"time"
)
type RegisteredAtValidator struct {}
func NewRegisteredAtValidator() *RegisteredAtValidator {
return &RegisteredAtValidator{}
}
func (v *RegisteredAtValidator) IsValid(d *InputData) bool {
limit := time.Date(2022, time.September, 1, 0, 0, 0, 0, time.UTC)
return d.RegisteredAt.After(limit)
}
func (v *RegisteredAtValidator) Done() {
fmt.Println("'RegisteredAt' is OK.")
}
func (v *RegisteredAtValidator) Fail() {
fmt.Println("'RegisteredAt' is NG.")
}
package main
import (
"fmt"
"regexp"
)
type PasswordValidator struct {}
func NewPasswordValidator() *PasswordValidator {
return &PasswordValidator{}
}
func (v *PasswordValidator) IsValid(d *InputData) bool {
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(d.Password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(d.Password)
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(d.Password)
return hasUpper && hasLower && hasNumber
}
func (v *PasswordValidator) Done() {
fmt.Println("'Password' is OK.")
}
func (v *PasswordValidator) Fail() {
fmt.Println("'Password' is NG.")
}
その他
package main
import "time"
type InputData struct {
Id int
RegisteredAt time.Time
Password string
}
func NewInputData(id int, registeredAt time.Time, password string) *InputData {
return &InputData{
Id: id,
RegisteredAt: registeredAt,
Password: password,
}
}
動作確認
package main
import (
"time"
"fmt"
)
func main() {
// OK
data1 := NewInputData(200, time.Date(2022, time.October, 1, 0, 0, 0, 0, time.UTC), "Password1")
// IDがNG
data2 := NewInputData(10, time.Date(2022, time.October, 1, 0, 0, 0, 0, time.UTC), "Password1")
// 登録日がNG
data3 := NewInputData(200, time.Date(2021, time.October, 1, 0, 0, 0, 0, time.UTC), "Password1")
// パスワードがNG
data4 := NewInputData(200, time.Date(2022, time.October, 1, 0, 0, 0, 0, time.UTC), "password")
iv := NewValidator(NewIdValidator())
rv := NewValidator(NewRegisteredAtValidator())
pv := NewValidator(NewPasswordValidator())
iv.SetNext(rv).SetNext(pv)
fmt.Println("data1 validation")
fmt.Println("Validation result: ", iv.Validate(data1))
fmt.Println()
fmt.Println("data2 validation")
fmt.Println("Validation result: ", iv.Validate(data2))
fmt.Println()
fmt.Println("data3 validation")
fmt.Println("Validation result: ", iv.Validate(data3))
fmt.Println()
fmt.Println("data4 validation")
fmt.Println("Validation result: ", iv.Validate(data4))
}
>> go run .
data1 validation
'ID' is OK.
'RegisteredAt' is OK.
'Password' is OK.
Validation result: true
data2 validation
'ID' is NG.
'RegisteredAt' is OK.
'Password' is OK.
Validation result: false
data3 validation
'ID' is OK.
'RegisteredAt' is NG.
'Password' is OK.
Validation result: false
data4 validation
'ID' is OK.
'RegisteredAt' is OK.
'Password' is NG.
Validation result: false
コメント