1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Golangで、デザインパターン「Chain of Responsibility」を学ぶ

Posted at

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

W3sDesign_Chain_of_Responsibility_Design_Pattern_UML.jpg

UML class diagram

designpattern-chain_of_responsibility01.gif
(以上、「ITエンジニアのための技術支援サイト by IT専科」より引用)

□ 備忘録

Chain of Responsibilityパターンは、昔のお役所仕事的な「たらい回し」な業務スタイルを連想できます。
人に要求がやってくる。その人がそれを処理できれば処理をする。処理できないなら、その要求を「次の人」にたらい回しする。次の人がそれを処理できるなら処理する。処理できなければ、その要求を「さらに次に人」にたらい回しする。
Chain of Responsibilityパターンは、所謂、線形リストのような処理モデルと理解しました。

Chain_of_responsibility.png

■ "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メソッドでした。

chain_of_responsibility/support.go
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の各構造体が、この役を努めます。

chain_of_responsibility/support.go
// 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
}
chain_of_responsibility/support.go
// 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
}
chain_of_responsibility/support.go
// 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
}
chain_of_responsibility/support.go
// 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関数が、この役を努めます。

Main.go
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) その他

トラブル番号を一元管理します。

trouble.py
class Trouble:
    def __init__(self, number):
        self.__number = number

    def getNumber(self):
        return self.__number

    def __str__(self):
        return '[Trouble {0}]'.format(self.__number)

■ 参考URL

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?