Goでデザインパターン (Chain of Responsibilityパターン)

Goデザインパターン

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

コメント

タイトルとURLをコピーしました