この記事は ちょうぜつ本 1 周年 Advent Calendar 2023 16日目です。
1周年おめでとうございます!🎉
よろしければ皆さんもお祝いしましょう!
アーキテクチャとか、原則とか、パターンとか、イラストかわいいとか、なんでもOKですよ。
はじめに
変更しやすいコードが書けないのにソフトウェア開発とか舐めているのですか
天使様1ごめんなさい〜、nikkieです。
「かわいい」と技術書が夢の合体を果たした、ちょうぜつ本(『ちょうぜつソフトウェア設計入門』)!🤗
読書会を共同主催しており、第8章「デザインパターン」を読み進めています。
先日の読書会で完全に理解できたMediatorについて取り上げます。
目次
前回のちょうぜつ本!
前から順に読み進めて第8章に突入し、読書会では範囲を細かくしてじっくりと読み進めています。
- 読書ログ | #ちょうぜつ本 第8章 Template Methodパターン 〜穴埋め問題にする〜 - nikkie-ftnextの日記
- 読書&写経ログ | #ちょうぜつ本 第8章 Bridgeパターン 〜has-a関係でとらえて、複数系統の組合せを表現できる!〜 - nikkie-ftnextの日記
- 読書ログ | #ちょうぜつ本 (第8章)でも #fukabori (48)でも、ただしSingleton、テメーはダメだ - nikkie-ftnextの日記
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
- 実装するインターフェースは2つ
- メンバー(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インスタンスが必要
- ObjectAのインスタンス化には、MediatorInterfaceAを実装したクラスのインスタンスが必要
- MediatorはMediatorInterfaceAを実装したクラスではある
そうすると
- 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についても同様です)
ちょうぜつ本のサンプルコードでは
MediatorInterfaceA
はnotify_activity_done
メソッドを持つ(活動完了を通知)MediatorInterfaceB
はnotify_task_completion
メソッドを持つ(タスク完了を通知)Mediator
はMediatorInterfaceA
もMediatorInterfaceB
も実装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自体ちょうぜつ本ではじめまして概念でしたが、実装方法がマジで分からなかったのを乗り越えての完全理解です。
読書会の場で「自身を渡すんじゃないですか」と教えていただいたことに感謝です。ありがとうございました