LoginSignup
7
8

More than 1 year has passed since last update.

Pythonで、デザインパターン「Observer」を学ぶ

Last updated at Posted at 2020-01-31

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

■ Observerパターン(オブザーバ・パターン)

Observerパターンとは、プログラム内のオブジェクトのイベント( 事象 )を他のオブジェクトへ通知する処理で使われるデザインパターンの一種。
通知するオブジェクト側が、通知されるオブジェクト側に観察(英: observe)される形になる事から、こう呼ばれる。
出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。
分散イベント処理システムの実装にも使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。

UML class and sequence diagram

W3sDesign_Observer_Design_Pattern_UML.jpg

UML class diagram

2880px-Observer_w_update.svg.png

(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

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クラスが、この役を努めます。

observer/generator.py
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クラスが、この役を努めます。

observer/generator.py
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クラスが、この役を努めます。

observer/observer.py
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クラスが、この役を努めます。

observer/observer.py
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メソッドが、この役を努めます。

Main.py
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()

■ 参考URL

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