設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他のDesign Patternもちょくちょく出していきます。
前置き
- 増補改訂版Java言語で学ぶデザインパターン入門をJavaからPythonにしてます。(Pythonは3.4.2)
- githubにコード置いてあります(まだ動かないものもある)
デザインパターンをどういう時に、何を、どう使うのかを理解することが一先ずの目標。
(Javaというか静的型付言語は初めてで、且つpython歴もそんなに長くないので、Pythonistaぽっくないところがあると思います。ご指摘ございましたらご教授ください。)
今回は、振る舞いに関するパターンObserver。
Observerとは
Observerパターンでは、観察対象の状態が変化すると、観察者に対して通知されます。Observerパターンは、状態変化に応じた処理を記述するときに有効です。
概要
ここで作るサンプルプログラムは、数をたくさん生成するオブジェクトを観察者が観察して、その値を表示するというものです。ただし、表示の方法は観察者によって異なります。DigitObserverは値を数字で表示しますが、GraphObserverは値を簡易グラフで表示します。
全体のクラス図
from abc import ABCMeta, abstractmethod
class Observer(Exception):
__meta__ = ABCMeta
@abstractmethod
def update(self, generator):
pass
observerインタフェースは、「観察者」を表現するインタフェースです。具体的な観察者は、このインタフェースで実装します。
updateメソッドを呼び出すのは、数を生成するNumberGeneratorです。
updateメソッドはNumberGeneratorが「私の内容が更新されました。表示の方も更新してください」とobserverに伝えるためのメソッドです。
from abc import ABCMeta, abstractmethod
class NumberGenerator(metaclass=ABCMeta):
__observers = []
def add_observer(self, observer):
self.__observers.append(observer)
def delete_observer(self, observer):
self.__observers.remove(observer)
def notify_observers(self):
for observer in self.__observers:
observer.update(self)
@abstractmethod
def get_number():
pass
@abstractmethod
def execute():
pass
NumberGeneratorクラスは数を生成する抽象クラスです。実際の数の生成(executeメソッド)と、数を取得する部分(get_numberメソッド)はサブクラスが実装することを期待して、抽象メソッドになっています。
observersフィールドは、NumberGeneratorを観察しているobserverたちを保存しているフィールドです。
add_observerはobserverを追加するメソッド、delete_observerはobserverを削除するメソッドです。
notify_observersメソッドは、observer全員に対して「私の内容が更新されたので、あなたの表示を更新してください」と伝えるものです。このメソッドの中では、observersの中のobserverたち1人1人のupdateメソッドを呼び出しています。
import random
from number_generator import NumberGenerator
class RandomNumberGenerator(NumberGenerator):
__number = 0
def __init__(self):
self.__rand = random
def get_number(self):
return self.__number
def execute(self):
for i in range(0, 20):
self.__number = self.__rand.randint(0, 50)
self.notify_observers()
RandomNumberGeneratorクラスは、NumberGeneratorのサブクラスで乱数を発生するものです。
randomフィールドには、乱数発生器が保持され、numberフィールドには現在の乱数値が保持されます。
get_numberメソッドは、numberフィールドの値を返します。
executeメソッドは乱数(0~49の整数)を20個生成し、そのつどnotify_observersを使って、観察者に通知をします。
import time
import logging
from observer import Observer
class DigitObserver(Observer):
def update(self, generator):
print('DigitObserver:' + str(generator.get_number()))
try:
time.sleep(1)
except InterruptedError as e:
logging.exception(e)
DigitObserverクラスは、observerインタフェースを実装しているクラスで、観察した数を「数字」で表示するためのものです。updateメソッドの中で引数として与えられたNumberGeneratorのget_numberメソッドを使って数を取得し、表示します。表示の様子がわかるように間隔をあけています。
import sys
import time
from observer import Observer
class GraphObserver(Observer):
def update(self, generator):
sys.stdout.write('GraphObserver:')
count = generator.get_number()
for i in range(0, count):
sys.stdout.write('*')
print('')
try:
time.sleep(1)
except InterruptedError:
pass
GraphObserverクラスも、Observerインタフェースを実装しているクラスです。このクラスは観察した数を*****のような「簡易グラフ」で表します。
from digit_observer import DigitObserver
from graph_observer import GraphObserver
from random_number_generator import RandomNumberGenerator
def main():
generator = RandomNumberGenerator()
observer1 = DigitObserver()
observer2 = GraphObserver()
generator.add_observer(observer1)
generator.add_observer(observer2)
generator.execute()
if __name__ == "__main__":
main()
RandomNumberGeneratorのインスタンスを1個作り、その観察者を2個作ります。observer1はDigitObserverの、observer2はGraphObserverのインスタンスです。
add_observerメソッドを使って観察者を登録した後、generator.executeを使って数を生成します。
実行結果
DigitObserver:17
GraphObserver:*****************
DigitObserver:43
GraphObserver:*******************************************
DigitObserver:47
GraphObserver:***********************************************
DigitObserver:34
GraphObserver:**********************************
DigitObserver:30
GraphObserver:******************************
DigitObserver:50
GraphObserver:**************************************************
DigitObserver:7
GraphObserver:*******
DigitObserver:40
GraphObserver:****************************************
DigitObserver:39
GraphObserver:***************************************
DigitObserver:41
GraphObserver:*****************************************
DigitObserver:38
GraphObserver:**************************************
DigitObserver:3
GraphObserver:***
DigitObserver:22
GraphObserver:**********************
DigitObserver:26
GraphObserver:**************************
DigitObserver:0
GraphObserver:
DigitObserver:23
GraphObserver:***********************
まとめ
一方のオブジェクトがもう一方のオブジェクト(観察者) を登録するメソッドを追加することによって、監視対象となりました。監視対象オブジェクトはその変更時に登録オブザーバーにメッセージを送信します。
オブザーバーがその情報をどう処理するかは、監視対象オブジェクトにとっては関係なく、どんなクラスのオブジェクトでも構いません。
オブジェクトは必ずしも理由を理解することなく、その結果、オブジェクト間の依存度を下げることができ、通知先の管理を観察者が行うことで、観察対象は通知側を意識する必要が無くなりました。