Observerパターン

概要

データの更新を複数のオブジェクト(オブザーバ)に通知する」パターンです。
「データの更新」と「その他処理」の責任を分離することで、ソースコードの柔軟性を増します。

Railsなどで使用されている「MVC(モデル・ビュー・コントローラー)」もこのパターンの一種です。

例題

数字を変更したときに、

  • 数字を表示する画面
  • 数字をグラフとして表示する画面

の2つの画面を同時に変更することを考えます。

(「グラフとして表示」は数字の大きさの分米印をつけることで対応します。)

サンプルコード

def main():
    m = ConcreteModel()
    m.add_observer(NumView())
    m.add_observer(ExcelView())
    for i in [5, 10, 15]:
        m.num = i


# Subject
class Model:
    def __init__(self):
        self.__observers = []
        self.__num = None

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

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


# ConcreteSubject
class ConcreteModel(Model):
    def __init__(self):
        super().__init__()

    @property
    def num(self):
        return self.__num

    @num.setter
    def num(self, num):
        self.__num = num
        self.notify_observer()  # important point


class Observer:
    def __init__(self):
        pass

    def update():
        pass


# ConcreteObserver1
class NumView(Observer):
    def __init__(self):
        pass

    # ここも大事
    def update(self, model):
        s = "NView: {}".format(model.num)
        print(s)


# ConcreteObserver2
class ExcelView(Observer):
    def __init__(self):
        pass

    # ここも大事
    def update(self, model):
        s = "EView: {}".format("*" * model.num)
        print(s)


if __name__ == "__main__":
    main()

# 出力

NView: 5 EView: ***** NView: 10 EView: ********** NView: 15 EView: ***************

特徴

「データの更新」の処理と「表示」の処理を切り分けることが目的です。

データ更新を担う側から見ると、このようになっています。

class ConcreteModel(Model):
    ...

    # 実際はModelクラスにある
    def notify_observer(self):
        for observer in self.__observers:
            observer.update(self)

    @num.setter
    def num(self, num):
        self.__num = num
        self.notify_observer()  # important point

データ更新を担う側から見ると、データ更新される側のupdateを起動するだけです。
詳細な処理は(Concrete)Model側は知りません。

データ更新される側から見ると、このようになっています。

class ExcelView(Observer):
    ... 

    def update(self, model):
        s = "EView: {}".format("*" * model.num)
        print(s)

Observerを継承し、updateメソッドを実装すると、
データ更新する側から更新されたデータが届きます。

データ更新される側はデータが来たら処理をする、という処理のみを実装すればOKです。

Pythonらしく書くためのコツ

特になし

クラス図

# 一般的なクラス図

一般的なクラス図(Observerパターン)

# サンプルコードにおけるクラス図

サンプルコードにおけるクラス図(Observerパターン)

参考URL

実践Python3(例題をお借りしました)