4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他の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:***********************

まとめ

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

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

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

参考

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?