GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Chain of Responsibility」を学ぶ"
今回は、Pythonで実装した”Chain of Responsibility”のサンプルアプリをGolangで実装し直してみました。
■ Chain of Responsibility(チェーン オブ レスポンシビリティ・パターン)
「Chain of Responsibility」という英単語は、「責任の連鎖」を意味します。
このパターンは、ある要求の受取り対象となる複数のオブジェクトに鎖状の関係を構築し、要求を処理する事が可能なオブジェクトに渡るまで、順次、構築した鎖状の関係に沿って要求を受流していくパターンです。
このパターンを適用すると、「この要求はこのオブジェクトが処理する」などという司令塔的な役割り(結び付き)を利用者側が意識しなくて良くなり、利用者側は「連鎖関係にある任意のオブジェクトに要求を投げるだけ」、処理側は「流れてきた要求が自身で処理できる場合は処理し、できない場合は、その要求を次のオブジェクトに渡すだけ」という役割分担が明確となります。(利用者側は、処理の詳細まで意識する必要はない)
UML class and sequence diagram
UML class diagram
(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)
□ 備忘録
Chain of Responsibility
パターンは、昔のお役所仕事的な「たらい回し」な業務スタイルを連想できます。
人に要求がやってくる。その人がそれを処理できれば処理をする。処理できないなら、その要求を「次の人」にたらい回しする。次の人がそれを処理できるなら処理する。処理できなければ、その要求を「さらに次に人」にたらい回しする。
Chain of Responsibility
パターンは、所謂、線形リストのような処理モデルと理解しました。
■ "Chain of Responsibility"のサンプルプログラム
実際に、Chain of Responsibilityパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- トラブル解決に必要な案件に対して、
トラブル番号
を採番して、以下のトラブル解決のたらい回しな業務フローに沿って、トラブルを解決する - トラブルが解決した場合は、その解決結果を出力する
- トラブルが解決できなければ、最終的に、"解決できず"で終了する
<トラブル解決のたらい回しな業務フロー>
(1) 事前に、たらい回しの順番を決めておく("Alice"->"Bob"->"Charlie"->"Diana"->"Elmo"->"Fred")
(2) まずは、Alice
がトラブル解決の要求を受け付ける。Alice
は、サポートしない役割なので、次のBod
に要求をたらい回しする
(3) Bod
が要求を受け付ける。Bod
は、**制限付きサポート(Trouble番号の上限"100")**の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のCharlie
に要求をたらい回しする
(4) Charlie
が要求を受け付ける。Charlie
は、**特別サポート(Trouble番号が"429"のみ)**の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のDiana
に要求をたらい回しする
(5) Diana
が要求を受け付ける。Diana
は、制限付きサポート(Trouble番号の上限"200")の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のElmo
に要求をたらい回しする
(6) Elmo
が要求を受け付ける。Elmo
は、Trouble番号が奇数のみサポートの役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、次のFred
に要求をたらい回しする
(7) Fred
が要求を受け付ける。Fred
は、**制限付きサポート(Trouble番号の上限"300")**の役割であり、その役割範囲で頑張り、解決できれば業務を終了する。解決できなければ、最終的に、"解決できず"で終了する
$ go run Main.go
[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].
なんか、よくわかりづらい出力結果になりました。
サンプルプログラムの出力結果を理解するには、サンプルプログラムの詳細を確認する方が早そうです。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Chain_of_Responsibility
- ディレクトリ構成
.
├── Main.go
└── chain_of_responsibility
├── support.go
└── trouble.go
(1) Handler(処理者)の役
Handler
役は、要求を処理するインタフェースを定める役です。
「次の人」を保持しておき、自分で処理ができない要求がきたら、その人にたらい回しします。もちろん、「次の人」もHandler
役です。
サンプルプログラムでは、support
構造体とsupportInterface
インタフェースが、この役を努めます。
要求を処理するメソッドは、Handle
メソッドでした。
package chainOfResponsibility
import "fmt"
type supportInterface interface {
resolve(trouble *Trouble) bool
Handle(trouble *Trouble)
SetNext(next supportInterface) supportInterface
}
type support struct {
name string
own supportInterface
next supportInterface
}
// SetNext func for relating to next Supporter
func (s *support) SetNext(next supportInterface) supportInterface {
s.next = next
return next
}
// Handle func for handling trouble
func (s *support) Handle(trouble *Trouble) {
if s.own.resolve(trouble) {
s.done(trouble)
} else if s.next != nil {
s.next.Handle(trouble)
} else {
s.fail(trouble)
}
}
func (s *support) print() string {
return fmt.Sprintf("[%s]", s.name)
}
func (s *support) done(trouble *Trouble) {
fmt.Printf("%s is resolved by %s.\n", trouble.print(), s.print())
}
func (s *support) fail(trouble *Trouble) {
fmt.Printf("%s cannot be resolved.\n", trouble.print())
}
(2) ConcreteHandler(具体的処理者)の役
ConcreteHandler
役は、要求を処理する具体的な役です。
サンプルプログラムでは、NoSupport
, LimitSupport
, OddSupport
, SpecialSupport
の各構造体が、この役を努めます。
// NoSupport is struct
type NoSupport struct {
*support
}
// NewNoSupport func for initializing NoSupport
func NewNoSupport(name string) *NoSupport {
noSupport := &NoSupport{
support: &support{
name: name,
},
}
noSupport.own = noSupport
return noSupport
}
func (n *NoSupport) resolve(trouble *Trouble) bool {
return false
}
// LimitSupport is struct
type LimitSupport struct {
*support
limit int
}
// NewLimitSupport func for initializing LimitSupport
func NewLimitSupport(name string, limit int) *LimitSupport {
limitSupport := &LimitSupport{
support: &support{
name: name,
},
limit: limit,
}
limitSupport.own = limitSupport
return limitSupport
}
func (l *LimitSupport) resolve(trouble *Trouble) bool {
if trouble.getNumber() < l.limit {
return true
}
return false
}
// OddSupport is struct
type OddSupport struct {
*support
}
// NewOddSupport func for initializing OddSupport
func NewOddSupport(name string) *OddSupport {
oddSupport := &OddSupport{
support: &support{
name: name,
},
}
oddSupport.own = oddSupport
return oddSupport
}
func (o *OddSupport) resolve(trouble *Trouble) bool {
if trouble.getNumber()%2 == 1 {
return true
}
return false
}
// SpecialSupport is struct
type SpecialSupport struct {
*support
number int
}
// NewSpecialSupport func for initializing SpecialSupport
func NewSpecialSupport(name string, number int) *SpecialSupport {
specialSupport := &SpecialSupport{
support: &support{
name: name,
},
number: number,
}
specialSupport.own = specialSupport
return specialSupport
}
func (s *SpecialSupport) resolve(trouble *Trouble) bool {
if trouble.getNumber() == s.number {
return true
}
return false
}
(3) Client(依頼人)の役
Client
役は、最初のConcreteHandler
役に要求を出す役です。
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
chainOfResponsibility "./chain_of_responsibility"
)
func startMain() {
alice := chainOfResponsibility.NewNoSupport("Alice")
bob := chainOfResponsibility.NewLimitSupport("Bob", 100)
charlie := chainOfResponsibility.NewSpecialSupport("Charlie", 429)
diana := chainOfResponsibility.NewLimitSupport("Diana", 200)
elmo := chainOfResponsibility.NewOddSupport("Elmo")
fred := chainOfResponsibility.NewLimitSupport("Fred", 300)
alice.SetNext(bob).SetNext(charlie).SetNext(diana).SetNext(elmo).SetNext(fred)
for i := 0; i < 500; i += 33 {
alice.Handle(chainOfResponsibility.NewTrouble(i))
}
}
func main() {
startMain()
}
(4) その他
トラブル番号を一元管理します。
class Trouble:
def __init__(self, number):
self.__number = number
def getNumber(self):
return self.__number
def __str__(self):
return '[Trouble {0}]'.format(self.__number)