Mediatorパターン

概要

オブジェクト同士のやりとりを仲介者(Mediator)により簡素化する」パターンです。

例題

ここでは、よくあるHTMLのフォームのバックエンド側のシステムを作ります。
システムの仕様は以下です。

  • テキストボックスにIDとパスワードを入れると、ボタンがアクティブになる
  • ボタンをクリックすると、IDとPWの認証をする

    なお、実際はHTMLは作成せず、TextFormクラス、Buttonクラスを作成し、やり取りをします。

サンプルコード

def main():
    textarea_id = TextArea("ID")
    textarea_pw = TextArea("PW")
    button = Button("Login")

    # buttonはMediatorに作用しても何もおきない
    mediator = Mediator(textarea_id, textarea_pw, button)

    # IDを入力
    textarea_id.change_text("hoge")

    # まだアクティブでないため、ボタンをおしてもなにもおきない
    button.click()                   # (Ignored as button is disabled...)

    # PWを入力(ここでアクティブになる)
    textarea_pw.change_text("huga")  # (Active Login Button)
    button.click()                   # (ID and PW is confirmed)


# Mediator
class Mediator:
    def __init__(self, obj_textarea_id, obj_textarea_pw, obj_button):
        self.obj_button = obj_button
        self.obj_textarea_id = obj_textarea_id
        self.obj_textarea_pw = obj_textarea_pw
        obj_button.mediator = self
        obj_textarea_id.mediator = self
        obj_textarea_pw.mediator = self

    # consultation()
    def on_change(self, component):
        if component.name == "ID" or component.name == "PW":
            self.update_ui()
        elif component.name == "Login":
            self.confirm_id_pw()

    def update_ui(self):
        if bool(self.obj_textarea_id.text) and bool(self.obj_textarea_pw.text):
            print("(Active login button)")
            self.obj_button.active = True

    def confirm_id_pw(self):
        if self.obj_textarea_id.text == "hoge" and self.obj_textarea_pw.text == "huga":
            print("(ID and PW is confirmed)")
        else:
            print("(ID or PW is incorrect)")


# Colleague
class Mediated:
    def __init__(self):
        self.mediator = None

    # advice
    def on_change(self):
        if self.mediator is not None:
            self.mediator.on_change(self)


# ConcreteColleague
class Button(Mediated):
    def __init__(self, name=""):
        super().__init__()
        self.active = False
        self.name = name

    def click(self):
        if self.active:
            self.on_change()
        else:
            print("(Ignored as button is disabled...)")


# ConcreteColleague
class TextArea(Mediated):
    def __init__(self, name=""):
        super().__init__()
        self.name = name
        self.text = ""

    def change_text(self, text):
        self.text = text
        self.on_change()


if __name__ == "__main__":
    main()

# 結果

(Ignored as button is disabled...) (Active login button) (ID and PW is confirmed)

なお、main文のmediatorをコメントアウトしても、エラーは起きないものの、ボタンがアクティブになりません。

特徴

# Mediator側

# Mediator
class Mediator:
    ...

    # consultation()
    def on_change(self, component):
        if component.name == "ID" or component.name == "PW":
            self.update_ui()
        elif component.name == "Login":
            self.confirm_id_pw()

Mediator側は、on_change関数が呼ばれたら、呼び出し元のobjectに従って、いろいろします。

Mediator側は、Mediated(= Colleague)側のすべてを知っている必要があります。

そのため、Mediatorクラスはどうしても複雑なものとなり、使い捨てに近いものとなる可能性は高いです。

# Mediated側(Colleague側)

# Colleague
class Mediated:
    def __init__(self):
        self.mediator = None

    # advice
    def on_change(self):
        if self.mediator is not None:
            self.mediator.on_change(self)

...


# ConcreteColleague
class TextArea(Mediated):
    def __init__(self, name=""):
        super().__init__()
        self.name = name
        self.text = ""

    def change_text(self, text):
        self.text = text
        self.on_change()

仲介されるクラスは、全てMediatedを継承する必要があります

子クラスで、self.on_change()(親クラスで定義した関数)を呼ぶことでMediatorに変化を通知することができます

Pythonらしく書くためのコツ

特になし

実践Python3にはコルーチンを用いた書き方もあるが、省略しました。

クラス図

# 一般的なクラス図

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

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

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

参考URL

実践Python3

Mediatorの例①

Mediatorの例②