デザインパターン(Design Pattern)#Observer


設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他のDesign Patternもちょくちょく出していきます。

前置き

デザインパターンをどういう時に、何を、どう使うのかを理解することが一先ずの目標。
(Javaというか静的型付言語は初めてで、且つpython歴もそんなに長くないので、Pythonistaぽっくないところがあると思います。ご指摘ございましたらご教授ください。)

今回は、振る舞いに関するパターンObserver。

Observerとは

Observerパターンでは、観察対象の状態が変化すると、観察者に対して通知されます。Observerパターンは、状態変化に応じた処理を記述するときに有効です。

概要

ここで作るサンプルプログラムは、数をたくさん生成するオブジェクトを観察者が観察して、その値を表示するというものです。ただし、表示の方法は観察者によって異なります。DigitObserverは値を数字で表示しますが、GraphObserverは値を簡易グラフで表示します。

全体のクラス図

observer.py
from abc import ABCMeta, abstractmethod


class Observer(Exception):
    __meta__ = ABCMeta

    @abstractmethod
    def update(self, generator):
        pass

observerインタフェースは、「観察者」を表現するインタフェースです。具体的な観察者は、このインタフェースで実装します。

updateメソッドを呼び出すのは、数を生成するNumberGeneratorです。
updateメソッドはNumberGeneratorが「私の内容が更新されました。表示の方も更新してください」とobserverに伝えるためのメソッドです。

number_generator.py
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メソッドを呼び出しています。

random_number_generator.py
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を使って、観察者に通知をします。

digit_observer.py
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メソッドを使って数を取得し、表示します。表示の様子がわかるように間隔をあけています。

graph_observer.py
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インタフェースを実装しているクラスです。このクラスは観察した数を*****のような「簡易グラフ」で表します。

main.py
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:***********************

まとめ

一方のオブジェクトがもう一方のオブジェクト(観察者) を登録するメソッドを追加することによって、監視対象となりました。監視対象オブジェクトはその変更時に登録オブザーバーにメッセージを送信します。

オブザーバーがその情報をどう処理するかは、監視対象オブジェクトにとっては関係なく、どんなクラスのオブジェクトでも構いません。

オブジェクトは必ずしも理由を理解することなく、その結果、オブジェクト間の依存度を下げることができ、通知先の管理を観察者が行うことで、観察対象は通知側を意識する必要が無くなりました。

参考