Mediatorパターンの使い方について考えたこと
GoFのmediatorパターンを採用すれば、シーン内のオブジェクトの通信を一つのクラス(mediatorクラス)に集約することができる。これにより次のような利点があると考えられる。
www.techscore.com
- シーン全体の通信のやりとりの様子がわかりやすくなり、デバッグしやすくなる。
- 各オブジェクトのメンバ変数は、自分自身の状態変数以外にはmediatorクラスのインスタンスしか必要なくなる。(外部とのやりとりは全てmediatorクラスに任せることが出来る。)
- シーン内の新しい機能追加に対してその通信に関するやりとりの変更はmediatorクラスにのみ影響するようになる(プログラムの変更の影響範囲が狭められる)
しかし、mediatorパターンを誤った使い方で用いてしまうと、このクラスの実装者はシーン内のオブジェクトの仕様ややりとりの様子を全て把握しなければいけなくなり、極端に実装の負担が増えてしまう。(Mediatorクラスのゴッドクラス化問題)
以前少しだけ、Reactフレームワークを勉強した時にReduxと呼ばれる Fluxアーキテクチャーについて学んだ。このFluxアーキテクチャにはMediatorパターンをより使いやすくするためのヒントがあるのではないかと自分は考える。
qiita.com
以上のことを踏まえて現時点で自分が考えるMediatorクラスの良い使い方を述べたい。
簡単にその実装のポイントを先に述べると次のようになる。
- ReduxにおけるActionの役割を持つMessageクラスまた列挙型Messageを定義する。
- MediatorクラスはPublicメソッドMediate(Message message)を持つ。(ReduxにおけるReducerの役割)
- CollegueクラスはMediatorに通信を発行するSealedメソッドSendMessage(Message message)と、Mediatorクラスからの通信を受け取るメソッドAcceptMessage(Message message)をもつ。
- Collegueクラスを継承したGameObjectではAcceptMessageのオーバーライドを行う。(Messageによる条件分岐が必要なので、実装方法はUnityのOnCollisionEnterメソッドのオーバーライドに似た実装を想定。)
//Colleagueクラス public class Colleague : MonoBehaviour{ [SerializeField] private Mediator mediator public sealed void SendMessage(Message message){ mediator.Mediate(message); } virtual protected void AcceptMessage(Message message){ //継承先で実装する } }
//Mediatorクラス(シーン間共通情報(所持金など)を持たせるのもありかも) public class Mediator : MonoBehaviour{ [SerializeField] private Colleague[] colleagues; public void Mediate(Message message){ foreach(var colleague in colleagues) colleague.AcceptMessage(message); } }
※Messageクラスまたはenum型Messageは必要に応じて状態を増やす。
これにより、Mediatorクラスは具体的な通信の内容まで知る必要がなくなるので、クラスの実装者の負担は最小限に抑えられる。
また、シーン内の要素追加などによる仕様変更についてもMediateメソッドの変更によって吸収できる。
課題
- MediateメソッドがCollegueクラスのSendMessage以外からも呼ばれてしまう。
- SendMessageがMediatorクラス以外からも呼ばれてしまう。
- Mediateメソッドは登録されてるCollegue全てのAcceptMessageメソッドを呼び出すので多くのクラスにとっては空メソッドを呼び出すことになってしまう。また、CollegueのAcceptMessageの条件分岐が間違えると誤った通信を行ってしまう危険性がある。