Mediatorパターン


 ★★☆

Mediatorパターン

オブジェクトの間の調整を行いながら処理をすすめる

Mediator とは、英語で「仲裁人、調停者」を意味する単語です。Mediator パターンとは、多数のオブジェクトの間の調整を行いながら処理をすすめる必要が ある場合に利用すると威力を発揮するパターンです。

例えば、システムの設計書などの資料を関係者が好き勝手に書き換えたとしたらどうなるでしょう?各自が、銘々に判断して、自分の都合の良いように変更する。こんな状態で、プロジェクト開発をうまく乗り切ることができるでしょうか?いくら皆が一所懸命システムをより良くしようとしたとしても、既存部分と整合性がとれなくなったり、システム仕様がむやみに膨らんでいくに違いありません。


そこで、仲裁人である「Mediator」の登場です。Mediator は、システムの進行状況を見渡し、変更の要不要を判断した上で、「許可」や「不可」の指示を出します。全てのプロジェクトメンバーは、Mediator の指示通りに変更をしたり止めたりするわけです。

Mediator パターンは、複数のオブジェクト間の調整をするために、各オブジェクトからの問い合わせを受け、適宜判断を行い指示を出す「仲裁人」の役割を果たすクラスを利用するパターンです。


Mediator パターンは、入力インタフェースなどに利用することができます。例えば、『複雑に絡み合ったある条件を満たさなければ「有効」にならないボタン』なんてものを作成したいときなどです。

「ラジオボタン Aが選択されている状態で、テキストボックスに入力がある場合」もしくは、「ラジオボタンB が選択されている状態で、チェックボックス C もしくは D にチェックが入っている場合」に「有効」になるボタンは、「ラジオボタン A」がチェックされたときに、「テキストボックスに入力があるか」を検証したり、ラジオボタン B が選択されたときに「チェックボックス C もしくは D がチェックされているか」を検証するようなプログラムにしていると、チェックボックス C にチェックが入れられたときや、チェックボックス D のチェックがはずされたときなども検証する必要があるプログラムになります。条件が複雑になるにつれ、どこで何をチェックしているのか管理できなくなってきてしまいます。

例えば、互いに通信しあうインスタンスの数が、2個ならば通信経路は2本ですが、これが3個になれば6本、4個になると12本に増え、5個になると20本、6個になると30本にもなります。

インスタンスの数が少ないときには問題は大きくありませんが、最初の設計のままどんどんインスタンスを増やしていくと、いずれ破綻をきたすことになります。


Mediator パターンでは、各オブジェクトは、自分の状態が変わったときに、Mediator に連絡し、 Mediator は、状態が変わったオブジェクトだけでなく、各オブジェクトの状態を総合的に判断し、「ボタン」を示すオブジェクトに、「有効」「無効」を伝えるような設計になり、管理が比較的楽になります。

つまり、各々が勝手に機能を呼び合うのでなく、必ず媒介者(仲人)を通して通信するようにしなさい。個々に勝手に通信先と話をせず、仲介人に通知先を判断してもらうようにしておきなさい、ということです。


Mediator パターンとは、オブジェクト間のやりとりそのものをカプセル化してクラスにしてしまったものです。このことによってオブジェクト間のやりとりに秩序ができ、メンテナンスしやすいものになります。


ただし、Mediator パターンは、オブジェクト指向の原則をはずしています。オブジェクト指向の面からいえば、オブジェクトはカプセル化されているべきであり、オブジェクトの変更が他のオブジェクトに影響を与えないようにすべきです。

しかし、Mediator パターンは、他のオブジェクト間のやりとりを Mediator 自身の中に持っていますので、他のオブジェクトの変更をもろに受けることになります。例えば、ひとつのオブジェクトを削除すれば、それとのやりとりも削除されなければならないからです。


例題

sw1、sw2、sw3 は 0.1秒ごとに状態の変わる可能性があります。そして、そのスイッチで次のような回路を作成しました。

電球の状態を表しなさい。

回路図
  ┌─SW1──SW2─┐
 ┌┤          ├─┐
 │└────SW3───┘ 電球
 │             │
 └────電池電池─────┘

実行結果 (一例)
○○○●○●○○○●●○○○○●  







Mediatorパターンを使用しない例

Switch.java
public class Switch extends Thread { private Circuit circuit; private boolean status = true; // スイッチ(閉) private boolean run = true; private int interval = 0; private void actionPerformed() { double d = Math.random(); boolean s = d > 0.5; if (status != s) { status = s; if (circuit.sw[2].getStatus() == true) circuit.lightOn(); // 点灯 else if (circuit.sw[0].getStatus() == false) circuit.lightOff(); // 消灯 else if (circuit.sw[1].getStatus() == false) circuit.lightOff(); // 消灯 else circuit.lightOn(); // 点灯 } } protected void start(int n) { interval = n + 500; start(); } public void run() { while(run) { actionPerformed(); try { Thread.sleep(interval); } catch(Exception x) {} } } protected void stop_() { run = false; } private boolean getStatus() { return status; } protected void setCircuit(Circuit circuit) { this.circuit = circuit; } }
Circuit.java
public class Circuit { protected Switch[] sw; private int n = 0; private boolean status = true; public Circuit() { sw = new Switch[3]; for (int i = 0; i < 3; i++) { sw[i] = new Switch(); sw[i].setCircuit(this); } } protected void lightOn() { System.out.print("○"); // 点灯 } protected void lightOff() { System.out.print("●"); // 消灯 if (++n >= 5) // 5回消えたら終わり stop(); } public void start() { Random rnd = new Random(); for (int i = 0; i < 3; i++) { int intResult = rnd.nextInt(5) + 1; // 1~5の乱数を取得 sw[i].start(100 * intResult); } } public void stop() { for (int i = 0; i < 3; i++) { sw[i].stop_(); } status = false; } public boolean getStatus() { return status; } }
Main.java
public class Main { public static void main(String[] args) throws Exception { Circuit circuit = new Circuit(); circuit.start(); while(true) { Thread.sleep(1000); if (circuit.getStatus() == false) break; } circuit.stop(); } }

Mediatorパターンを使用した例

Colleague.java
public class Colleague extends Thread { protected IMediator mediator; public void setMediator(IMediator mediator) { this.mediator = mediator; } }
Switch.java
public class Switch extends Colleague { private boolean status = true; // スイッチ(閉) private boolean run = true; private int interval = 0; private void actionPerformed() { double d = Math.random(); boolean s = d > 0.5; if (status != s) { // 状態が変わったら status = s; mediator.colleagueChanged(); // 通知 } } protected void start(int n) { interval = n + 500; start(); } public void run() { while(run) { actionPerformed(); try { Thread.sleep(interval); } catch(Exception x) {} } } protected void stop_() { run = false; } protected boolean getStatus() { return status; } }
IMediator.java
public interface IMediator { void colleagueChanged(); }
Circuit.java
public class Circuit implements IMediator { public Switch[] sw; private int n = 0; private boolean status = true; public Circuit() { sw = new Switch[3]; for (int i = 0; i < 3; i++) { sw[i] = new Switch(); sw[i].setMediator(this); } } public void colleagueChanged() { if (sw[2].getStatus() == true) lightOn(); // 点灯 else if (sw[0].getStatus() == false) lightOff(); // 消灯 else if (sw[1].getStatus() == false) lightOff(); // 消灯 else lightOn(); // 点灯 } private void lightOn() { System.out.print("○"); // 点灯 } private void lightOff() { System.out.print("●"); // 消灯 if (++n >= 5) // 5回消えたら終わり stop(); } public void start() { Random rnd = new Random(); for (int i = 0; i < 3; i++) { sw[i].start(100 * (rnd.nextInt(5) + 1)); } } public void stop() { for (int i = 0; i < 3; i++) { sw[i].stop_(); } status = false; } public boolean getStatus() { return status; } }
Main.java
public class Main { public static void main(String[] args) throws Exception { Circuit circuit = new Circuit(); circuit.start(); while(true) { Thread.sleep(1000); if (circuit.getStatus() == false) break; } circuit.stop(); } }

スイッチは0.1秒ごと状態が変化する可能性があります。そして、その都度、すべてのスイッチが他のスイッチの状況を確認し電球の状況を変更しています。これでは、条件が複雑になるにつれ、どこで何をチェックしているのか管理できなくなってきてしまいます。

この例では、スイッチという一種類だけのオブジェクトでしたが、これが何種類もあって、条件判定がそれぞれ違っていたらどうでしょうか。

そして、1つのオブジェクトの内容を改造したらどうなるでしょうか? 他のすべてのオブジェクトに影響を及ぼしてしまいます。新たなオブジェクト(例えば電池)を追加する場合も、それと関連する複数のオブジェクト(スイッチ)に何らかの改造が必要になるはずです。プログラムを改造したら、テストしなければなりません。たった1つのオブジェクトを改造や追加したことで、プログラムの多くの部分をテストし直さなければならなくなります。

Mediatorパターンで記述するとどうでしょう。状態が変わったときだけ呼び出され確認しています。

Mediator パターンでは、各オブジェクトは、自分の状態が変わったときに、Mediator に連絡し、 Mediator は、状態が変わったオブジェクトだけでなく、各オブジェクトの状態を総合的に判断し、 処理を行うわけです。

Circuit のインスタンスが生成されたとき、各スイッチのインスタンスも生成され、それらには、Circuit のインスタンスが設定されます。各スイッチは、状態が変わったら、Circuit のインスタンスの colleagueChanged() メソッドを呼び出します。colleagueChanged() メソッドは、各スイッチの状態を調べ、点灯か消灯かを表示します。

もし、スイッチを改造したり、電池などを追加したとしても、影響を及ぼすのは Mediator である Circuit だけで済みます。Circuit の中で、スイッチの改造を吸収してしまえば、スイッチや電池などの改造は不要になります。


オブジェクト指向プログラミングの成功の秘訣として「できるだけオブジェクト間の関連を少なくする」ということがあります。Mediatorパターンを採用することで、オブジェクト間の関連の数を少なくできます。相互に関連を持っていれば、その関連の数は、オブジェクトが4つなら6本ですし、オブジェクトが5つなら10本ですが、Mediatorパターンではオブジェクトが4つなら4本、5つなら5本で済みます。