GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Observerパターン(オブザーバ・パターン)
Observerパターンとは、プログラム内のオブジェクトのイベント( 事象 )を他のオブジェクトへ通知する処理で使われるデザインパターンの一種。
通知するオブジェクト側が、通知されるオブジェクト側に観察(英: observe)される形になる事から、こう呼ばれる。
出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。
分散イベント処理システムの実装にも使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。
UML class and sequence diagram
UML class diagram
□ 備忘録
Observer
パターンでは、観察対象の状態が変化すると、観察者に対して通知されるので、状態変化に応じた処理を記述するときに有効だそうです。
observer
という言葉の本来の意味は「観察者」ですが、実際には、Observer
役は能動的に「観察」するのではなく、Subject
役から「通知」されるのを受動的に待っていることになるので、Publish-Subscribe
パターンと呼ばれることもあるそうです。
確かに、publish(発行)
とsubscribe(購読)
という表現の方が、適切のような気がしました。
■ "Observer"のサンプルプログラム
実際に、Observerパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 数をたくさん生成するオブジェクトを観察者が観察して、その値を表示する仕組みになります
- 表示の方法は、観察者によって異なります。
-
DigitalObserver
は、値を数字で表示します -
GraphicObserver
は、値を簡易グラフで表示します
$ python Main.py
DigitObservser: 30
GraphicObserver:******************************
DigitObservser: 48
GraphicObserver:************************************************
DigitObservser: 6
GraphicObserver:******
DigitObservser: 19
GraphicObserver:*******************
DigitObservser: 19
GraphicObserver:*******************
DigitObservser: 45
GraphicObserver:*********************************************
DigitObservser: 8
GraphicObserver:********
DigitObservser: 21
GraphicObserver:*********************
DigitObservser: 40
GraphicObserver:****************************************
DigitObservser: 6
GraphicObserver:******
DigitObservser: 1
GraphicObserver:*
DigitObservser: 9
GraphicObserver:*********
DigitObservser: 26
GraphicObserver:**************************
DigitObservser: 22
GraphicObserver:**********************
DigitObservser: 16
GraphicObserver:****************
DigitObservser: 10
GraphicObserver:**********
DigitObservser: 45
GraphicObserver:*********************************************
DigitObservser: 1
GraphicObserver:*
DigitObservser: 36
GraphicObserver:************************************
DigitObservser: 45
GraphicObserver:*********************************************
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Observer
- ディレクトリ構成
.
├── Main.py
└── observer
├── __init__.py
├── generator.py
└── observer.py
(1) Subject(被験者)の役
Subject
役は、「観察される側」を表します。Subject
役は、観察者であるObserver
役を登録するメソッドと、削除するメソッドを持っています。また、「現在の状態を取得する」メソッドも宣言されています。
サンプルプログラムでは、NumberGenerator
クラスが、この役を努めます。
import random
from abc import ABCMeta, abstractmethod
class NumberGenerator(metaclass=ABCMeta):
def __init__(self):
self.__observers = []
def addObserver(self, observer):
self.__observers.append(observer)
def deleteObserver(self, observer):
self.__observers.remove(observer)
def notifyObserver(self):
for o in self.__observers:
o.update(self)
@abstractmethod
def getNumber(self):
pass
@abstractmethod
def execute(self):
pass
(2) ConcreteSubject(具体的な被験者)の役
ConcreteSubject
役は、具体的な「観察される側」を表現する役です。状態が変化したら、そのことを登録されているObserver
役に伝えます。
サンプルプログラムでは、RandomNumberGenerator
クラスが、この役を努めます。
class RandomNumberGenerator(NumberGenerator):
def __init__(self):
self.__number = 0
super(RandomNumberGenerator, self).__init__()
def getNumber(self):
return self.__number
def execute(self):
for _ in range(20):
self.__number = random.randint(0, 49)
self.notifyObserver()
(3) Observer(観察者)の役
Observer
役は、Subject
役から「状態が変化しましたよ」と教えてもらう役です。そのためのメソッドがupdate
です。
サンプルプログラムでは、Observer
クラスが、この役を努めます。
import time
from abc import ABCMeta, abstractmethod
class Observer(metaclass=ABCMeta):
@abstractmethod
def update(self, generator):
pass
(4) ConcreteObserver(具体的な観察者)の役
ConcreteObserver
役は、具体的なObserver
です。update
メソッドが呼び出されると、そのメソッドの中でSubject
役の現在の状態を取得します。
サンプルプログラムでは、DigitObserver
クラスとGraphObserver
クラスが、この役を努めます。
class DigitObserver(Observer):
def update(self, generator):
print("DigitObservser: {0}".format(generator.getNumber()))
time.sleep(0.1)
class GraphObserver(Observer):
def update(self, generator):
print("GraphicObserver:", end='')
count = generator.getNumber()
for _ in range(count):
print('*', end='')
print("")
time.sleep(0.1)
(5) Client(依頼人)の役
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
from observer.observer import DigitObserver, GraphObserver
from observer.generator import RandomNumberGenerator
def startMain():
generator = RandomNumberGenerator()
observer1 = DigitObserver()
observer2 = GraphObserver()
generator.addObserver(observer1)
generator.addObserver(observer2)
generator.execute()
if __name__ == '__main__':
startMain()