Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Observer
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Observer パターンの使われる場面◆◆

Web で調べたところ Observer の別名はイベントリスナーと言われているらしい。一番近いと思われる別名は Publish-Subscribe 発行ー購読だそうだ。いずれにしても,GoF が言う目的を必要とするところに使われる。

通知を出す側は受け取り側が何をしているかをまったく関知しないように,密な結合ではなくて,それでいて受け取り損なうことのないようになっているのがObserver パターンです。


(2023-11-18)Python3.11で動作確認済み


◆◆Observer パターンとは◆◆

GoFによれば,Observer パターンの目的は, 「あるオブジェクトが状態を変えたときに,それに依存するすべてのオブジェクトに自動的にそのことが知らされ,また,それらが更新されるように,オブジェクト間に一対多の依存関係を定義する。」

GoFによれば,Observery パターンの別名は,Dependents,Publish-Subscribe パターンである。

GoFによれば,次のような状況で Observer パターンを使う。
・抽象化により,2つの面が,一方が他方に依存しているという形で現れる場合。これらの面をそれぞれ別々のオブジェクトにカプセル化することにより,それらを独立に変更したり,再利用することが可能になる。
・1つのオブジェクトを変化させるときに,それに伴いその他のオブジェクトも変化させる必要があり,しかも変化させる必要があるオブジェクトを固定的に決められない場合。
・オブジェクトが,他のオブジェクトに対して,それがどのようなものなのかを仮定せずに通知できるようにする場合。別の言い方をすると,これらのオブジェクトを密に結合したくない場合。

GoFによれば,Observer パターンの関連するパターンは次のようなものである。
Mediator パターン:複雑な更新のセマンティクスをカプセル化することにより,ChangeManagerオブジェクトは,subject と observer の間で mediator として働く。
Singleton パターン:ChangeManager オブジェクトは,自身を唯一の存在とし,グローバルにアクセスできるようにするために,Singleton パターンを使う。

◆◆Observer パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
NumberGenerator                 ◇→           Observer
(addObserver,deleteObserver,    (Notifies)     (update)
 notifyObservers,                              ↑     ↑
 getNumber,execute)                   DigitObserver    GraphObserver
      ↑                               (update)         (update)
RandomNumberGenerator
(getNumber,execute)
class NumberGenerator は,通知の出し側です。
class Observer は,通知の受け取り側ですが抽象クラスです。
class DigitObserver と class GraphObserver は,通知の受け取り側の具象クラスです。
class DigitObserver は,数字のみを表示します。
class GraphObserver は,バーグラフを表示します。
他の表示方法でもかまわなく増減が可能です。
class RandomNumberGenerator は,乱数を発生させます。class NumberGenerator のオブジェクトの変化を起こすのが役割ですので内容は別のものでもかまいません。

◆◆Observer パターンの実装◆◆

いわゆるイベントドリブンなのがメソッド execute であり,通知の出し方を決めているのがメソッド execute から呼ばれているメソッド notifyObservers です。メソッド getNumber は通知の内容です。

通知の受け取り側のイベントドリブンは,メソッド update であり,通知の出し側のメソッド notifyObservers から起動されています。結局のところ,メソッド execute から呼ばれていることになります。

通知の受け取り側はリストになっていてその増減をやるのが,メソッド addObserver とメソッド deleteObserver です。

これですべてのメソッドの役割を説明しました。その詳細をみていきます。

メソッド execute とメソッド getNumber は入れ替えが可能になっています。そのためclass NumberGeneratorの方は抽象メソッドになっていて,入れ替え可能な class RandomNumberGeneratorの方は具象メソッドになっています。デバッグがやり易いようにコンストラクタで Random.seed に定数を入れています。これで乱数発生は同じものが繰り返されます。Javaサンプルはそうなっていませんのでコメントアウトしてもかまいません。

通知の受け取り側の増減はクライアント;メインルーチンでインスタンス化してからリストを操作します。また,class RandomNumberGenerator は入れ替え可能なのでインスタンス化してからイベントドリブンなメソッド execute を呼んでいます。

メソッド execute は通知内容を決定してメソッド notifyObservers を呼びます。このメソッドはリストにある通知の受け取り側のメソッド update を起動します。受け取り側は通知内容を様々な様式で表示します。

そして処理の遅れをシミュレーションするため time.sleep を入れてあります。応用では削除してください。パラメータは休止時間(秒)であり小数点でもよいです。デバッグは 0 を入れるとやり易いです。

デバッグのためメソッド execute に print を入れてあります。コメントアウトを外せば判りますが,通知の受け取りはすべて間に合います。その代わり,通知の出し側が待つことになります。このパターンを応用する場合は,これらの動作を正しく知った上で適用してください。

このパターンは工夫の余地がほとんどなく,Javaサンプルとほとんど同じになってしまいました。通知内容が単純なのでそうなるのでしょう。逆に応用がけっこう難しいかもかもしれません!?

◆◆ソースコード◆◆

このWebページの左上隅からダウンロードできます。

ソースファイルは1つです。
・Observer.py;Observer パターンのPythonサンプル

【Observer.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from random import randint, seed
import time
import logging
from abc import ABCMeta, abstractmethod

class Observer(metaclass=ABCMeta):

    @abstractmethod
    def update(self, generator):
        pass

class NumberGenerator(metaclass=ABCMeta):

    observers = []
    def addObserver(self, observer):
        NumberGenerator.observers.append(observer)
    def deleteObserver(self, observer):
        NumberGenerator.observers.remove(observer)
    def notifyObservers(self):
        it = iter(NumberGenerator.observers)
        for observer in it:
            observer.update(self)
    @abstractmethod
    def getNumber():
        pass
    @abstractmethod
    def execute():
        pass

class RandomNumberGenerator(NumberGenerator):
    number = 0
    def __init__(self):
        seed(314)
    def getNumber(self):
        return self.number
    def execute(self):
        for i in range(20):          #回数
            self.number = randint(0, 49)
            #print("before notify")
            self.notifyObservers()
            #print("after notify")
        #print("all finish")

class DigitObserver(Observer):
    def update(self, generator):
        sys.stdout.write("DigitObserver:{}".format(generator.getNumber()))
        sys.stdout.write("\n")
        try:
            time.sleep(1)
        except InterruptedError:
            pass

class GraphObserver(Observer):
    def update(self, generator):
        sys.stdout.write("GraphObserver:")
        count = generator.getNumber()
        sys.stdout.write("*" * count)
        sys.stdout.write("\n")
        try:
            time.sleep(1)
        except InterruptedError:
            pass

def main():
    generator = RandomNumberGenerator()
    observer1 = DigitObserver()
    observer2 = GraphObserver()
    generator.addObserver(observer1)
    generator.addObserver(observer2)
    generator.execute()

if __name__ == "__main__":
    main()
"""標準出力
DigitObserver:12
GraphObserver:************
DigitObserver:29
GraphObserver:*****************************
DigitObserver:7
GraphObserver:*******
以下省略
"""

以上

トップページに戻る
inserted by FC2 system