はじめに
「Java で学ぶデザインパターン入門」を読んでいるときに、Meditator パターンを見つけました。このデザインパターンを使うと、コンポーネント間の依存関係が複雑に絡み合うことを防げると思い、Vue で実装してみました。
サンプルコードだけ知りたい方はこちらからどうぞ。
Meditator パターンとは
Meditator パターンでの主な登場人物は二人です。
Meditator
- Colleague と通信を行なう
- Colleague を操作するロジックを持つ
Colleague
- Meditator と通信を行なう
- 基本的には Meditator にイベントを通知し、値を受け取り、描画するだけ
- 自分自身に閉じる view のロジックは持っても良い
以下のような流れで処理が進みます。
①:親である Meditator に子である Colleague がイベントを通知
②:Colleague からのイベントを元に Meditator でロジックを実行
③:Meditator の実行結果を Colleague に通知
④:必要に応じて Colleague は自身を再描画
Meditator パターンを採用するメリット
Meditator パターンを採用するメリットはロジックの集約だと思っています。ロジックを集約するレイヤー(Meditator)を作ることで、
- 機能の仕様を簡単に理解できる
- コンポーネント間の依存関係が一方向になる(Meditator ⇄ Colleague)
- コンパイルで発見できないロジックのバグを見つけやすくなる
というメリットを享受できます。
ログイン時にゲストとログインユーザーを切り替えるチェックボックスの例を考えます。Guest か Login かのどちらかしか選択できないとします。
Meditator パターンを採用しない場合
図の横方向(黄色)と縦方向(オレンジ)の両方向に矢印があることに注目してください。これは依存関係が複数方向にあることを意味しています。子コンポーネント間での値のやりとりを許した結果、親と子で値の整合性が取れなくなっています。これはコンパイル時にエラーにならないので非常に見つけにくいバグです。
では、子コンポーネント間の変更を親コンポーネントに通知し、親コンポーネントの値を変更すれば良いのではと思うかもしれません。しかし、子コンポーネントの量が増えた場合には、ロジックを追う際に見なくてはいけないファイル数がかなり増えます。実際の現場では、10個以上のファイルにロジックがまたがることもあります。そういった場合に、複数方向に依存性が絡み合ったコードを読み解き、仕様を理解するのはなかなか辛い作業で、ミスも発生するリスクが上がります。
Meditator パターンを採用する場合
図の矢印の向きが縦方向(オレンジ)のみであることに注目してください。これは依存関係が単方向であることを意味しており、ロジックによるバグがあった場合に見つけやすいです。また、ロジックが親であるログインフレームに集約されているので、ロジックを変更する際の影響範囲も一目瞭然です。ロジックが集約されているのでログイン機能の仕様も簡単に把握できます。新しく、ジョインする開発者にもここを見てと言えばいいのです。
Mdeitator パターンを採用した実装例
「Java で学ぶデザインパターン入門」で紹介されいた例を、Vue で実装してみました。ただ、ファイルの見通しをよくするために、独自に Colleague を集約したレイヤーを設けています。
システム構成
- Vue.js: v2.6.14
- Nuxt: v2.15.8
- Vuetify: v2.6.1
図
① Meditator
以下の役割
- ログイン機能のロジックを扱うレイヤー
- 上述の仕様がまとまっている
- Colleague を集約したレイヤー(②)からのイベントを受け取り、ロジックを実行、値を渡す
- Colleague を集約したレイヤー(②)のコンポーネント作成、初期値の代入
② Colleague の集約
以下の役割
- Colleague(③) を集約し、Meditator(①) と Colleague(③) を繋ぐレイヤー
- Colleague(③) からのイベントを Meditator(①) に通知
- Meditator(①) からの値を Colleague(③) に渡す
- Colleague(③) の作成、初期値の代入
③ Colleague
以下の役割
- Colleague を集約したレイヤー(②)にイベントを通知し、自身の view を作成するレイヤー
- Colleague を集約したレイヤー(②)にイベントを通知
- Colleague を集約したレイヤー(②)から値を受け取り、描画
- 描画に関するロジックは持っている
仕様
コード
コードの全容を知りたい方はこちらからどうぞ。
ここでは、LoginTextFields を例に上述の ①, ②, ③ をどのように実装しているのかを紹介します。
① Meditator
上記で色分けしてした仕様の知識がすべてこのレイヤーに納められています。
② Colleague の集約
- LoginTextField を作成
- LoginFrame から props で値を受け取り、LoginTextField に渡す
- LoginTextField からイベントを受け取り、LoginFrame にイベントを通知
③ Colleague
- LoginTextFields から props で値を受け取り、描画
- LoginTextFields にイベント通知
まとめ
Meditator パターンはロジックを集約することで、以下のようなメリットを享受できます。
- 機能の仕様を簡単に理解できる
- コンポーネント間の依存関係が一方向になる(Meditator ⇄ Colleague)
- コンパイルで発見できないロジックのバグを見つけやすくなる
その結果、コンポーネント間の依存関係を解消でき、機能の仕様を Meditator の実装を読むことで理解できます。フロントとの相性が良いデザインパターンだと思います。
参考
Java で学ぶデザインパターン入門