LoginSignup
3
3

More than 3 years have passed since last update.

Golangで、デザインパターン「Observer」を学ぶ

Posted at

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Observer」を学ぶ"

今回は、Pythonで実装した”Observer”のサンプルアプリをGolangで実装し直してみました。

■ Observerパターン(オブザーバ・パターン)

Observerパターンとは、プログラム内のオブジェクトのイベント( 事象 )を他のオブジェクトへ通知する処理で使われるデザインパターンの一種。
通知するオブジェクト側が、通知されるオブジェクト側に観察(英: observe)される形になる事から、こう呼ばれる。
出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。
分散イベント処理システムの実装にも使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。

UML class and sequence diagram

W3sDesign_Observer_Design_Pattern_UML.jpg

UML class diagram

2880px-Observer_w_update.svg.png

(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

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構造体が、この役を努めます。

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

observer/generator.go
// 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インタフェースが、この役を努めます。

observer/observer.go
package observer

import (
    "fmt"
    "time"
)

// Observer is interface
type Observer interface {
    update(generator *NumberGenerator)
}

(4) ConcreteObserver(具体的な観察者)の役

ConcreteObserver役は、具体的なObserverです。updateメソッドが呼び出されると、そのメソッドの中でSubject役の現在の状態を取得します。
サンプルプログラムでは、DigitObserver構造体とGraphObserver構造体が、この役を努めます。

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

Main.go
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()
}

■ 参考URL

3
3
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
3
3