nikkie-ftnextの日記

イベントレポートや読書メモを発信

読書&写経ログ | #ちょうぜつ本 第8章 Mediatorパターン 〜ColleagueたちにMediator自身を渡して、必ずMediatorを介在させる!〜

この記事は ちょうぜつ本 1 周年 Advent Calendar 2023 16日目です。
1周年おめでとうございます!🎉
よろしければ皆さんもお祝いしましょう!

アーキテクチャとか、原則とか、パターンとか、イラストかわいいとか、なんでもOKですよ。

はじめに

変更しやすいコードが書けないのにソフトウェア開発とか舐めているのですか

天使様1ごめんなさい〜、nikkieです。

「かわいい」と技術書が夢の合体を果たした、ちょうぜつ本(『ちょうぜつソフトウェア設計入門』)!🤗
読書会を共同主催しており、第8章「デザインパターン」を読み進めています。
先日の読書会で完全に理解できたMediatorについて取り上げます。

目次

前回のちょうぜつ本!

前から順に読み進めて第8章に突入し、読書会では範囲を細かくしてじっくりと読み進めています。

Facadeと並んで解説されたのが、Mediatorパターン!

Mediatorパターン

ファサードがビルの入り口にいる受付さんだとすれば、メディエーターはビルに入っている会社の内務マネージャーさんにあたります。(Kindle版 p.367)

「Mediatorとは何か」について、ちょうぜつ本の解説ももちろん分かりやすいのですが、元になったアドベントカレンダーが私にピタッとハマりました

Mediatorは、飲み会の会計を取りまとめてくれる人

個々のオブジェクトは、この調停者オブジェクトとしか、忘年会費の支払い取引をしてはいけないことにします。

個々人で貸し借りしたらかなり複雑になりますよね(N人いたらN-1の2乗のコミュニケーションパスらしい)。
お金のやり取りはMediatorとのやり取りだけを強制すればN-1に減る!

後述する実装の参考に眺めた結城先生の『Java言語で学ぶデザインパターン入門2ではMediator章の副題が

相手は相談役一人だけ

メンバー相互に自由にコミュニケーションさせるわけではなく、相談役を通すように強制するのですね。

結城先生本によると、GoF本からの用語として

  • 相談役:mediator
  • メンバー:colleague

と呼ぶそうです。

サンプルコードはどう動かせばいいの?

Mediatorという概念を完全に理解したので、ちょうぜつ本のサンプルコードをPythonで動かしていきます。

  • 相談役:Mediator
    • 実装するインターフェースは2つ
      • MediatorInterfaceA
      • MediatorInterfaceB
  • メンバー(colleague) 2つ
    • ObjectA
    • ObjectB

これをcolleagueは必ずMediatorとやり取りするように実装します。

PHPのクラスのプロパティはどんなふうに初期化されるの?

ObjectAの抜粋です

class ObjectA
{
    protected MediatorInterfaceA $mediator;
}

Mediatorの抜粋です

class Mediator implements MediatorInterfaceA
{
    protected ObjectA $a;
}

型宣言付きのクラスのプロパティです。
これらがどう初期化されるかが分かりませんでした。
言い換えると、ObjectAやMediatorクラスの定義は分かるのですが、これらを組み合わせてアプリケーションにするときに、それぞれどうインスタンス化すればいいか私は初見で分からなかったです。

PHPのクラスのプロパティ

PHPのドキュメントを当たります。

クラスのメンバ変数のことを プロパティ といいます。

型宣言に2例ありました。

1例目は__constructを持つ実装です。

class User
{
    public int $id;
    public ?string $name;

    public function __construct(int $id, ?string $name)
    {
        // 省略
    }
}

$user = new User(1234, null);

2例目はプロパティのsetterを実装しています。

class Shape
{
    public int $numberOfSides;
    public string $name;

    public function setNumberOfSides(int $numberOfSides): void
    {
        $this->numberOfSides = $numberOfSides;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    // getterは省略
}

ところが、ちょうぜつ本のコードはこの2例のどちらとも一致しないんですよ3
プロパティ$mediator$aにはどんなオブジェクトが入るの??
ここが一番知りたいのに分からん!となっていました

無限後退しちゃうじゃん!🤯

PHPのクラスのプロパティ(メンバ変数)は、Pythonではインスタンス変数として実装しようと考えました。
実装案は__init__インスタンス変数を設定というものです4
__init__の引数(厳密にはクラス呼び出しの引数)として外から渡す(=依存性注入)と考えたときに、

そうすると

  • MediatorにはObjectAが必要
  • Mediatorに必要なObjectAにはMediatorが必要(仮説)
  • Mediatorに必要なObjectAに必要なMediatorにはObjectAが必要
  • ...

際限なく遡っていきますよね(鶏がさきか卵がさきか、のように)

そうなると、「Mediatorに必要なObjectAにはMediatorが必要」という仮説が誤りなのかと思うのですが、「じゃあMediator以外でMediatorInterfaceAを実装したクラスってサンプルコードにないのでどうするんだろう?」と分からなくなります。

現在の理解:colleagueたちにMediator自身を渡して初期化する!

読書会で「ここがわからないんです〜」と話したことと、結城先生の『Java言語で学ぶデザインパターン入門』の実装から、ObjectAのインスタンス化にはMediator自身を渡すと考えて不明点は解消しました。

class ObjectA:
    def __init__(self, mediator: MediatorInterfaceA) -> None:
        self.mediator = mediator

class Mediator(MediatorInterfaceA):
    def __init__(self):
        self.a = ObjectA(self)  # Mediator自身をcolleagueに渡す

(ObjectBについても同様です)

ちょうぜつ本のサンプルコードでは

  • MediatorInterfaceAnotify_activity_doneメソッドを持つ(活動完了を通知)
  • MediatorInterfaceBnotify_task_completionメソッドを持つ(タスク完了を通知)
  • MediatorMediatorInterfaceAMediatorInterfaceBも実装
    • notify_activity_doneメソッドもnotify_task_completionメソッドも持つ

Mediatorクラスのインスタンスmediator)のnotify_activity_doneメソッドを呼ぶと

mediator = Mediator()
mediator.notify_activity_done()

以下のシーケンス図のようにMediatorとcolleaguesのメソッドが呼び出されます

  • mediatorはbのdo_taskを呼ぶ
  • bはdo_taskの最後に、mediator(MediatorInterfaceBを実装したクラスのインスタンスでもある)のnotify_task_completionを呼ぶ
  • mediatorはnotify_task_completionで、aのfinish_the_workメソッドを呼ぶ

colleague bがdo_taskの中でaのfinish_the_workを呼ぶわけではなく、Mediatorを介在させています!
colleagueどうしは相互に知っている必要はなく、Mediatorを呼び出すだけ。
Mediatorから別のcolleagueのメソッドが呼ばれます。
この関係を作りたいのでMediator自身を渡すわけですね!

Pythonで写経した実装の全容はこちらにあります。

終わりに

ちょうぜつ本 8章よりMediatorパターンでした。
colleagueは全員Mediatorを知っている状態にして

  • colleagueどうしのコミュニケーションは禁止
  • Mediatorを絶対介在させる(あるcolleagueがMediatorを呼ぶと、Mediatorが別のcolleagueを呼び出す)

これで相互の依存状態を整理できます!
Mediator自体ちょうぜつ本ではじめまして概念でしたが、実装方法がマジで分からなかったのを乗り越えての完全理解です。
読書会の場で「自身を渡すんじゃないですか」と教えていただいたことに感謝です。ありがとうございました


  1. ちょうぜつ本読書ログシリーズではおなじみのこちらの書き出し。元は『お隣の天使様にいつの間にか駄目人間にされていた件』の「家事ができないのに一人暮らしとか舐めているのですか」です
  2. 今回参照したのは増補改訂版(第2版)です。今から買うなら最新の3版がよさそうでした。「わかりやすさはそのままに、取り扱う例や解説を現代の観点から修正しました(依存性の注入、セキュリティに関する記述の追加など)。
  3. 1例目で自明な__constructを省略している?
  4. propertyのsetterとして実装する方法もあると思います