GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Observer」を学ぶ"
今回は、Pythonで実装した”Observer”のサンプルアプリをGolangで実装し直してみました。
■ Observerパターン(オブザーバ・パターン)
Observerパターンとは、プログラム内のオブジェクトのイベント( 事象 )を他のオブジェクトへ通知する処理で使われるデザインパターンの一種。
通知するオブジェクト側が、通知されるオブジェクト側に観察(英: observe)される形になる事から、こう呼ばれる。
出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。
分散イベント処理システムの実装にも使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。
UML class and sequence diagram
UML class diagram
□ 備忘録
Observer
パターンでは、観察対象の状態が変化すると、観察者に対して通知されるので、状態変化に応じた処理を記述するときに有効だそうです。
observer
という言葉の本来の意味は「観察者」ですが、実際には、Observer
役は能動的に「観察」するのではなく、Subject
役から「通知」されるのを受動的に待っていることになるので、Publish-Subscribe
パターンと呼ばれることもあるそうです。
確かに、publish(発行)
とsubscribe(購読)
という表現の方が、適切のような気がしました。
■ "Observer"のサンプルプログラム
実際に、Observerパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 数をたくさん生成するオブジェクトを観察者が観察して、その値を表示する仕組みになります
- 表示の方法は、観察者によって異なります。
-
DigitalObserver
は、値を数字で表示します -
GraphicObserver
は、値を簡易グラフで表示します
$ go run Main.go
DigitObservser: 10
GraphicObserver:**********
DigitObservser: 18
GraphicObserver:******************
DigitObservser: 14
GraphicObserver:**************
DigitObservser: 28
GraphicObserver:****************************
DigitObservser: 22
GraphicObserver:**********************
DigitObservser: 45
GraphicObserver:*********************************************
DigitObservser: 38
GraphicObserver:**************************************
DigitObservser: 37
GraphicObserver:*************************************
DigitObservser: 30
GraphicObserver:******************************
DigitObservser: 9
GraphicObserver:*********
DigitObservser: 9
GraphicObserver:*********
DigitObservser: 46
GraphicObserver:**********************************************
DigitObservser: 21
GraphicObserver:*********************
DigitObservser: 2
GraphicObserver:**
DigitObservser: 36
GraphicObserver:************************************
DigitObservser: 31
GraphicObserver:*******************************
DigitObservser: 4
GraphicObserver:****
DigitObservser: 27
GraphicObserver:***************************
DigitObservser: 33
GraphicObserver:*********************************
DigitObservser: 25
GraphicObserver:*************************
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Observer
- ディレクトリ構成
.
├── Main.go
└── observer
├── generator.go
└── observer.go
(1) Subject(被験者)の役
Subject
役は、「観察される側」を表します。Subject
役は、観察者であるObserver
役を登録するメソッドと、削除するメソッドを持っています。また、「現在の状態を取得する」メソッドも宣言されています。
サンプルプログラムでは、NumberGenerator
構造体が、この役を努めます。
package observer
import (
"math/rand"
"time"
)
// NumberGenerator is struct
type NumberGenerator struct {
number int
observers []Observer
}
// AddObserver func for adding Observer
func (n *NumberGenerator) AddObserver(observer Observer) {
n.observers = append(n.observers, observer)
}
func (n *NumberGenerator) notifyObserver() {
for _, o := range n.observers {
o.update(n)
}
}
func (n *NumberGenerator) getNumber() int {
return n.number
}
(2) ConcreteSubject(具体的な被験者)の役
ConcreteSubject
役は、具体的な「観察される側」を表現する役です。状態が変化したら、そのことを登録されているObserver
役に伝えます。
サンプルプログラムでは、RandomNumberGenerator
構造体が、この役を努めます。
// RandomNumberGenerator is struct
type RandomNumberGenerator struct {
*NumberGenerator
}
// NewRandomNumberGenerator func for initializing RandomNumberGenerator
func NewRandomNumberGenerator() *RandomNumberGenerator {
return &RandomNumberGenerator{
NumberGenerator: &NumberGenerator{number: 0},
}
}
// Execute func for executing something
func (r *RandomNumberGenerator) Execute() {
for i := 0; i < 20; i++ {
rand.Seed(time.Now().UnixNano())
r.number = rand.Intn(49)
r.notifyObserver()
}
}
(3) Observer(観察者)の役
Observer
役は、Subject
役から「状態が変化しましたよ」と教えてもらう役です。そのためのメソッドがupdate
です。
サンプルプログラムでは、Observer
インタフェースが、この役を努めます。
package observer
import (
"fmt"
"time"
)
// Observer is interface
type Observer interface {
update(generator *NumberGenerator)
}
(4) ConcreteObserver(具体的な観察者)の役
ConcreteObserver
役は、具体的なObserver
です。update
メソッドが呼び出されると、そのメソッドの中でSubject
役の現在の状態を取得します。
サンプルプログラムでは、DigitObserver
構造体とGraphObserver
構造体が、この役を努めます。
// DigitObserver is struct
type DigitObserver struct {
}
// NewDigitObserver func for initializing DigitObserver
func NewDigitObserver() *DigitObserver {
return &DigitObserver{}
}
func (d *DigitObserver) update(generator *NumberGenerator) {
fmt.Printf("DigitObservser: %d\n", generator.getNumber())
time.Sleep(time.Millisecond * 100)
}
// GraphObserver is struct
type GraphObserver struct {
}
// NewGraphObserver func for initializing GraphObserver
func NewGraphObserver() *GraphObserver {
return &GraphObserver{}
}
func (g *GraphObserver) update(generator *NumberGenerator) {
fmt.Printf("GraphicObserver:")
count := generator.getNumber()
for i := 0; i < count; i++ {
fmt.Printf("*")
}
fmt.Println("")
time.Sleep(time.Millisecond * 100)
}
(5) Client(依頼人)の役
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
"./observer"
)
func startMain() {
generator := observer.NewRandomNumberGenerator()
observer1 := observer.NewDigitObserver()
observer2 := observer.NewGraphObserver()
generator.AddObserver(observer1)
generator.AddObserver(observer2)
generator.Execute()
}
func main() {
startMain()
}