見出し画像

Pythonでデザインパターンを学ぼう[Observer]

Pythonを用いてのObserverパターンの実装方法について解説します。
Observerパターンは、「振る舞い関するパターン」に分類されるデザインパターンです。

Observerパターンとは?

observerパターンには主に大きく分けて二つの役割が存在します。

観察対象(Subject)
[Observer]に通知処理を行う。

観察者(Observer)
[Subject] の状態変化を監視する。
Subjectから受けた通知によって、処理を行う。

観察対象(Subject)の何らかの状態が変化した際に、Observer(観察者)に通知を行うデザインパターンです。通知を受けたObserver(観察者)は状態変化に応じた処理を行います。
そのためPublish/subscribeモデルと言ったほうが正しいのかもしれません。

Observerパターンのクラス図

以下がObserverパターンのクラス図です。ここでは4つのクラスが登場します。

・抽象的な観察対象(Subject)
・抽象的な観察者(Observer)
上記二つは抽象クラス、又はインターフェースです。

・観察対象(ConcreteSubject)
・観察者(ConcreteObserver)
[抽象的な観察対象],[抽象的な観察者]を継承して、こちら二つに具体的な実装をしていきます。

簡単なObserverパターンのサンプル

・観察対象(Subject)が乱数を20回生成
・二つの観察者(Observer)が、観察対象(Subject)の監視を行い、観察対象(Subject)の状態に変化に応じた処理を行う

1つ目の観察者(Observer)は、観察した数を数字で表示します

DigitObservser: 30

2つ目の観察者(Observer)は、観察した数を「*」の個数で表示します

******************************

Subject(観察対象者)の実装

まず最初にSubject(観察対象者)の乱数を生成する処理を記述します。
今回は上のクラス図通り、抽象クラスを作成します。

乱数を生成するだけの簡易的な処理なため、具象クラスだけを実装してもかまいませんが、Subject(観察対象者)が増えてきたり、処理内容が複雑してきたりすることから、親クラスである抽象クラスを作成します。

抽象クラスである AbstractNumberGenerator を作成します。
数字を取得する get_number , 処理を実行する execute メソッドに関しては、抽象メソッドとして扱い、サブクラスに実装処理を書かせます。

抽象クラスを継承させることで、抽象メソッドを実装させることを強制させます。

それではAbstractNumberGeneratorを作成してみます。

# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod

class AbstractNumberGenerator(metaclass=ABCMeta):
   """generate number abstract class """

   def __init__(self):
       self.__observers = []

   def add_observer(self, observer):
       self.__observers.append(observer)

   def delete_observer(self, observer):
       self.__observers.remove(observer)

   def notify_observer(self):
       for o in self.__observers:
           o.update(self)


   @abstractmethod
   def get_number(self) -> int:
       pass

   @abstractmethod
   def execute(self) -> None:
       pass

上記の抽象クラスはobserversというList型の変数(配列)を保持しています。(Pythonでは__と入れることがプライベート変数として扱われます)

・add_observer
引数にobserverを取り、通知先であるobserver(観察者)をインスタンス変数である__observersに追加していきます。

・delete_observer
引数にobserverを取り、通知先であるobserver(観察者)ををインスタンス変数である__observersから削除していきます。

・notify_observer
通知処理を行います。__observersのリストから、observer(観察者)のオブジェクトを取り出していき、observerオブジェクトのupdateメソッドを実行していくことで、通知処理を行っていきます。

Subject(観察対象者)の具体的な実装

それではSubject(観察対象者)の具体的な実装を行っていきます。抽象クラスであるAbstractNumberGeneratorを継承し、抽象メソッドであるget_numberexecuteメソッドの実装を行っていきます。

それでは実装クラスであるRandomNumberGeneratorを作成していきましょう。

executeメソッドで乱数の生成を行った後に、抽象クラスで実装されているnotify_observerメソッドを呼び出しています。

class RandomNumberGenerator(AbstractNumberGenerator):
   """ generate number class """

   def __init__(self):
       super(RandomNumberGenerator, self).__init__()
       self.number = 0

   def get_number(self):
       return self.number

   def execute(self):
       for _ in range(20):
           self.number = random.randint(0, 49)
           self.notify_observer()

Observer(観察者)の実装

次に観察対象者(Subject)の状態が変化して、通知を受けた際に応じた処理を行うObserver(観察者)の実装を行っていきましょう。

updateメソッドには引数にgeneratorを取るようにします。これはAbstractNumberGeneratorクラスを継承したクラスのインスタンスを引数として受け取るようにします。

# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod

class AbstractObserver(metaclass=ABCMeta):

   """ observer abstract class """
   @abstractmethod
   def update(self, ganerator):
       pass

Observer(観察者)の具体的な実装

先程作成したAbstractObserverクラスを継承し、updateメソッドを実装します。ここでのサンプルでは、下記の通り、二つのObserver(観察者)を作成します。

・1つ目の観察者(Observer)は、観察した数を数字で表示
・2つ目の観察者(Observer)は、観察した数を「*」の個数で表示

AbstractObserverを継承した「観察した数を数字で表示」を行うDegitObserverクラスと、「観察した数を「*」の個数で表示」を行うGraphObserverクラスを作成していきます。

# -*- coding: utf-8 -*-
import time

class DegitObserver(AbstractObserver):

    def update(self, generator):
        print('DigitObservser: {}'.format(
            generator.get_number()
        ))
        time.sleep(0.1)

class GraphObserver(AbstractObserver):

    def update(self, generator):
        count = generator.get_number()
        for _ in range(count):
            print('*', end='')
        print()
        time.sleep(0.1)

具象クラスである上記二つのupdateメソッドは、RandomNumberGeneratorのnotify_observerメソッドから呼びだされるようになっています。

Observer(観察者)とSubject(観察対象者)を扱う処理

これまでは、ObserverとSubjectの作成しました。それではこの両方を呼び出す処理を作成していきましょう。

# -*- coding: utf-8 -*-
from generator import RandomNumberGenerator
from observer import DegitObserver, GraphObserver

def main():
    #乱数生成を行うSubject(観察対象者)の作成
    generator = RandomNumberGenerator()

    #二つのObserverを作成する(観察者)
    degit_observer = DegitObserver()
    graph_observer = GraphObserver()

    #それぞれのObserver(観察者)を登録する
    generator.add_observer(degit_observer)
    generator.add_observer(graph_observer)

    #処理を実行
    #上記二つのobserverインスタンスに通知が行われる
    generator.execute()

if __name__ == '__main__':
    main()

Observerパターンの処理の内容のおさらいをします。

1. Subject(観察対象者)を作成(上記サンプルなら乱数を生成するRandomNumberGenerator)
2. Subjectの状態が変化した際に、notify_observerメソッドを実行(通知処理を行う)
3.Observer(観察者)インスタンスのupdateメソッドが実行される

Observerパターンの使い道とは?

Observerパターンは、実質は一番上でも説明したとおり、Publish/subscribeモデルと言ったほうが正しいです。

Subject(観察対象者)が通知処理を行うPublisherの役目を担い、Observer(観察者)が通知を受け取って処理を行うSubscriberの役目を担うと考えることができます。

そのため典型的な使い道としては以下の通りとなると考えられます。

・何らかのイベントが発生した際に、それに対応した処理を行う
・メーリングリストで特定の購読者にメッセージを送る

他に使い道などがあれば、是非教えてください。

上記のサンプルコードはこちらにまとめてあります

参考資料
デザインパターン「Observer」
Observer パターン (あるオブジェクトの変化を, それに依存するオブジェクトに自動的に知らせる仕組みを提供する)
TypeScriptで学ぶObserverパターン
Pythonによるデザインパターン[Observer]

この記事が気に入ったらサポートをしてみませんか?