Mediator
相手は相談役一人だけ
概要
Mediator:調停者(相談役)に全て投げる
相談役は各人からの要求をすべて受け取り、各人に指示を出す
ただし相談役は仕事の詳細についてはとやかく言わない
マンガでわかる Mediator
マンガでわかる Mediator #デザインパターン - Qiita
でざぱたんで覚える Mediator
ちびキャラは「メディエイターたん」。正体も活動内容も明かさない覆面ヒーローで、町の人々は互いを知らなくても、影の仲介者が繋ぐから平和が回る。相互参照を仲介者に集約する疎結合がMediatorの本質で、相手が増えても安全に追加できる。ただし仲介者に背負わせすぎると結局カオス(神様Mediator化)という、ヒーローの危うさと重なる警告も。
出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.168〜)
登場人物
- Mediator: 相談役。Colleagueに指示を出すインタフェース
- ConcreteMediator
- Colleage: 仕事をするインタフェース
- ConcreteColleage
クラス図

このサイトの実装(ログイン画面)での対応関係:
classDiagram
class Mediator {
<<interface>>
+createColleagues()
+colleagueChanged()
}
class Colleague {
<<interface>>
+setMediator(mediator)
+setColleagueEnabled(enabled)
}
class Frame
class Button
class Checkbox
class TextField
class LoginFrame {
-checkGuest ColleagueCheckbox
-checkLogin ColleagueCheckbox
-textUser ColleagueTextField
-textPass ColleagueTextField
-buttonOk ColleagueButton
-buttonCancel ColleagueButton
+createColleagues()
+colleagueChanged()
+actionPerformed(e)
}
class ColleagueButton {
-mediator Mediator
+setMediator(mediator)
+setColleagueEnabled(enabled)
}
class ColleagueCheckbox {
-mediator Mediator
+setMediator(mediator)
+setColleagueEnabled(enabled)
+itemStateChanged(e)
}
class ColleagueTextField {
-mediator Mediator
+setMediator(mediator)
+setColleagueEnabled(enabled)
+textValueChanged(e)
}
Frame <|-- LoginFrame
Mediator <|.. LoginFrame
Button <|-- ColleagueButton
Colleague <|.. ColleagueButton
Checkbox <|-- ColleagueCheckbox
Colleague <|.. ColleagueCheckbox
TextField <|-- ColleagueTextField
Colleague <|.. ColleagueTextField
LoginFrame o-- ColleagueButton
LoginFrame o-- ColleagueCheckbox
LoginFrame o-- ColleagueTextField
ColleagueButton ..> Mediator : mediator
ColleagueCheckbox ..> Mediator : mediator
ColleagueTextField ..> Mediator : mediator
やり方
メリット(用途)
- お互いがお互いをコントロールしようとして依存し合う状況を解消できる
疎結合の実現
例えば、100個のオブジェクトが相互にメッセージを送りあうとする 1このオブジェクトはそれぞれが99個の相手へのアクセスを持つので、 99^2 = 9801の関係グラフ線ができるようになる。
ところがMediatorをかまして1個のオブジェクトは1人のMediatorとだけやり取りするようにすると、 Mediatorが100人のアクセスを持っていれば9700個の関係グラフ線を節約できる
登場人物
- メディエータ:調停者。コリーグAの相談をコリーグBに投げる
- コリーグ: 同僚。メディエータに対してだけ相談を投げる、コリーグからのメッセージを受け取る
デメリット
Mediatorの処理は複雑になる。あらゆる調停に関する処理、すなわち個々のオブジェクトに関する情報を全て有していないといけないため。
Java
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.CheckboxGroup;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class LoginFrame extends Frame implements ActionListener, Mediator {
private ColleagueCheckbox checkGuest;
private ColleagueCheckbox checkLogin;
private ColleagueTextField textUser;
private ColleagueTextField textPass;
private ColleagueButton buttonOk;
private ColleagueButton buttonCancel;
// コンストラクタ。
// Colleagueたちを生成し、配置した後に表示を行う。
public LoginFrame(String title) {
super(title);
setBackground(Color.lightGray);
// レイアウトマネージャを使って4×2のグリッドを作る
setLayout(new GridLayout(4, 2));
// Colleagueたちの生成
createColleagues();
// 配置
add(checkGuest);
add(checkLogin);
add(new Label("Username:"));
add(textUser);
add(new Label("Password:"));
add(textPass);
add(buttonOk);
add(buttonCancel);
// 有効/無効の初期指定
colleagueChanged();
// 表示
pack();
show();
}
// Colleagueたちを生成する。
public void createColleagues() {
// 生成
CheckboxGroup g = new CheckboxGroup();
checkGuest = new ColleagueCheckbox("Guest", g, true);
checkLogin = new ColleagueCheckbox("Login", g, false);
textUser = new ColleagueTextField("", 10);
textPass = new ColleagueTextField("", 10);
textPass.setEchoChar('*');
buttonOk = new ColleagueButton("OK");
buttonCancel = new ColleagueButton("Cancel");
// Mediatorのセット
checkGuest.setMediator(this);
checkLogin.setMediator(this);
textUser.setMediator(this);
textPass.setMediator(this);
buttonOk.setMediator(this);
buttonCancel.setMediator(this);
// Listenerのセット
checkGuest.addItemListener(checkGuest);
checkLogin.addItemListener(checkLogin);
textUser.addTextListener(textUser);
textPass.addTextListener(textPass);
buttonOk.addActionListener(this);
buttonCancel.addActionListener(this);
}
// Colleageからの通知で各Colleageの有効/無効を判定する。
public void colleagueChanged() {
if (checkGuest.getState()) { // Guest mode
textUser.setColleagueEnabled(false);
textPass.setColleagueEnabled(false);
buttonOk.setColleagueEnabled(true);
} else { // Login mode
textUser.setColleagueEnabled(true);
userpassChanged();
}
}
// textUserまたはtextPassの変更があった。
// 各Colleageの有効/無効を判定する。
private void userpassChanged() {
if (textUser.getText().length() > 0) {
textPass.setColleagueEnabled(true);
if (textPass.getText().length() > 0) {
buttonOk.setColleagueEnabled(true);
} else {
buttonOk.setColleagueEnabled(false);
}
} else {
textPass.setColleagueEnabled(false);
buttonOk.setColleagueEnabled(false);
}
}
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
System.exit(0);
}
}
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague {
private Mediator mediator;
public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) { // コンストラクタ
super(caption, group, state);
}
public void setMediator(Mediator mediator) { // Mediatorを保持
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) { // Mediatorから有効/無効が指示される
setEnabled(enabled);
}
public void itemStateChanged(ItemEvent e) { // 状態が変化したらMediatorに通知
mediator.colleagueChanged();
}
}
public interface Colleague {
public abstract void setMediator(Mediator mediator);
public abstract void setColleagueEnabled(boolean enabled);
}
import java.awt.*;
import java.awt.event.*;
public class Main {
static public void main(String args[]) {
new LoginFrame("Mediator Sample");
}
}
import java.awt.Button;
public class ColleagueButton extends Button implements Colleague {
private Mediator mediator;
public ColleagueButton(String caption) {
super(caption);
}
public void setMediator(Mediator mediator) { // Mediatorを保持
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) { // Mediatorから有効/無効が指示される
setEnabled(enabled);
}
}
import java.awt.TextField;
import java.awt.Color;
import java.awt.event.TextListener;
import java.awt.event.TextEvent;
public class ColleagueTextField extends TextField implements TextListener, Colleague {
private Mediator mediator;
public ColleagueTextField(String text, int columns) { // コンストラクタ
super(text, columns);
}
public void setMediator(Mediator mediator) { // Mediatorを保持
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) { // Mediatorから有効/無効が指示される
setEnabled(enabled);
setBackground(enabled ? Color.white : Color.lightGray);
}
public void textValueChanged(TextEvent e) { // 文字列が変化したらMediatorに通知
mediator.colleagueChanged();
}
}
public interface Mediator {
public abstract void createColleagues();
public abstract void colleagueChanged();
}
Go
Java版はAWT/SwingのGUI(ユーザー名/パスワード欄、Guest/Loginのラジオボタン、OK/Cancelボタン)を表示し、マウス/キー操作でColleague間の有効/無効が連動する様子を見せるサンプル。GUIを持たないGo版では、各Colleague(チェックボックス・テキストフィールド・ボタン)を状態(選択/入力値/有効無効)を持つオブジェクトとして表現し、「ラジオボタンを選ぶ」「テキストを入力する」「ボタンを押す」という操作をメソッド呼び出しでシミュレートする。操作の度にLoginFrameが全Colleagueの状態をコンソールに出力する。
Colleagueはinterfaceで表現。Java版のColleagueはただのinterfaceで、3つの具象クラスがいずれもsetMediator()を「mediatorフィールドへ代入するだけ」の同じ一行実装で重複して持っているが、Goでは埋め込み(embedding)でこの重複をbaseColleagueにまとめている(ChainOfResponsibility版のbaseSupportと同じ方針)。
実行: go run ./GoF/patterns/Mediator/go
package main
// Mediator パターン: Mediator層 (Java版の Mediator.java 相当)
// 単体では実行不可。エントリポイントは main.go (go run ./GoF/patterns/Mediator/go)。
// Mediator はJava版 Mediator.java 相当のインタフェース。
// ConcreteMediator(LoginFrame)がCreateColleagues/ColleagueChangedを実装する。
type Mediator interface {
CreateColleagues()
ColleagueChanged()
}
package main
// Mediator パターン: Colleague層 (Java版の Colleague.java 相当)
// 単体では実行不可。エントリポイントは main.go (go run ./GoF/patterns/Mediator/go)。
//
// Java版のColleagueはただのinterfaceで、3つの具象クラス(ColleagueCheckbox/
// ColleagueButton/ColleagueTextField)はいずれもsetMediator()を「mediatorフィールドへ
// 代入するだけ」の同じ一行実装で持っている(Javaのinterfaceはフィールドや実装を持てない
// ため、3クラスがこの1行を重複して書いている)。Goでは埋め込み(embedding)でこの重複を
// 1箇所(baseColleague)にまとめられるので、ChainOfResponsibility版Goの baseSupport と
// 同じ方針でそうする。
// Colleague はJava版 Colleague.java 相当のインタフェース。
type Colleague interface {
SetMediator(mediator Mediator)
SetColleagueEnabled(enabled bool)
}
// baseColleague は3つの具象Colleagueが共有する「mediatorを覚えておくだけ」の状態と
// SetMediator実装をまとめたもの。SetColleagueEnabledは各具象型ごとに意味が異なる
// (無視/背景色付き有効化/クリック可否)ため、ここには置かない。
type baseColleague struct {
mediator Mediator
}
// SetMediator はMediatorを保持する(Java版の各setMediator()相当)。
func (b *baseColleague) SetMediator(mediator Mediator) {
b.mediator = mediator
}
package main
import "fmt"
// ColleagueCheckbox層 (Java版の ColleagueCheckbox.java 相当)
// 単体では実行不可。エントリポイントは main.go (go run ./GoF/patterns/Mediator/go)。
// CheckboxGroup はJava版 java.awt.CheckboxGroup 相当。同じグループに属する
// ColleagueCheckboxはラジオボタンとして排他選択される(1つを選ぶと他は自動的に外れる)。
type CheckboxGroup struct {
selected *ColleagueCheckbox
}
// ColleagueCheckbox はJava版 ColleagueCheckbox.java 相当のConcreteColleague。
// Guest/Loginのラジオボタンとして使われる。
type ColleagueCheckbox struct {
baseColleague
caption string
group *CheckboxGroup
state bool // 選択されているか(Java版 Checkbox.getState() 相当)
}
// NewColleagueCheckbox はコンストラクタ相当。java.awt.Checkbox(caption, group, state)に対応。
func NewColleagueCheckbox(caption string, group *CheckboxGroup, state bool) *ColleagueCheckbox {
cb := &ColleagueCheckbox{caption: caption, group: group, state: state}
if state {
group.selected = cb
}
return cb
}
// SetColleagueEnabled はMediatorからの指示で有効/無効を切り替える。
// Java版サンプルではGuest/Loginのラジオボタン自体が無効化されることは無いが、
// Colleagueインタフェースを満たすために実装しておく(no-op)。
func (c *ColleagueCheckbox) SetColleagueEnabled(enabled bool) {
// no-op
}
// Select はラジオボタンをクリックした操作をシミュレートする(Java版 itemStateChanged 相当)。
// 既に選択済みのボタンを選んでもAWTのCheckboxGroupはItemEventを発火しないため、
// ここでも同じ挙動にする。
func (c *ColleagueCheckbox) Select() {
if c.group.selected == c {
return
}
if c.group.selected != nil {
c.group.selected.state = false
}
c.group.selected = c
c.state = true
c.mediator.ColleagueChanged()
}
// String は fmt.Stringer を満たす(GUIが無い代わりの状態表示)。
func (c *ColleagueCheckbox) String() string {
return fmt.Sprintf("[%-5s] checked=%v", c.caption, c.state)
}
package main
import "fmt"
// ColleagueTextField層 (Java版の ColleagueTextField.java 相当)
// 単体では実行不可。エントリポイントは main.go (go run ./GoF/patterns/Mediator/go)。
// ColleagueTextField はJava版 ColleagueTextField.java 相当のConcreteColleague。
// Username/Passwordの入力欄として使われる。
type ColleagueTextField struct {
baseColleague
label string // java.awt.TextFieldには無い、コンソール表示用の追加情報(Username/Password)
text string
enabled bool
}
// NewColleagueTextField はコンストラクタ相当。
func NewColleagueTextField(label string) *ColleagueTextField {
return &ColleagueTextField{label: label, enabled: true}
}
// Text はテキスト欄の現在の入力値を返す(Java版 TextField.getText() 相当)。
func (t *ColleagueTextField) Text() string {
return t.text
}
// SetColleagueEnabled はMediatorから有効/無効が指示される
// (Java版は合わせて背景色もwhite/lightGrayに切り替えるが、GUIが無いのでここでは状態のみ)。
func (t *ColleagueTextField) SetColleagueEnabled(enabled bool) {
t.enabled = enabled
}
// SetText はテキスト入力操作をシミュレートする(Java版 textValueChanged 相当)。
// 無効化されている間はSwingのTextField同様キー入力を受け付けないので、ここでも入力を無視する。
func (t *ColleagueTextField) SetText(text string) {
if !t.enabled {
fmt.Printf(" * %s is disabled; input %q ignored\n", t.label, text)
return
}
t.text = text
t.mediator.ColleagueChanged()
}
// String は fmt.Stringer を満たす(GUIが無い代わりの状態表示)。
func (t *ColleagueTextField) String() string {
return fmt.Sprintf("[%-8s] enabled=%-5v text=%q", t.label, t.enabled, t.text)
}
package main
import "fmt"
// ColleagueButton層 (Java版の ColleagueButton.java 相当)
// 単体では実行不可。エントリポイントは main.go (go run ./GoF/patterns/Mediator/go)。
// ColleagueButton はJava版 ColleagueButton.java 相当のConcreteColleague。OK/Cancelボタン。
type ColleagueButton struct {
baseColleague
caption string
enabled bool
}
// NewColleagueButton はコンストラクタ相当。
func NewColleagueButton(caption string) *ColleagueButton {
return &ColleagueButton{caption: caption, enabled: true}
}
// SetColleagueEnabled はMediatorから有効/無効が指示される。
func (b *ColleagueButton) SetColleagueEnabled(enabled bool) {
b.enabled = enabled
}
// Click はボタン押下をシミュレートする(Java版 actionPerformed 相当。ただしJava版はOK/Cancel
// どちらを押してもSystem.exit(0)するが、ここではexitさせずログ出力だけに留める)。
func (b *ColleagueButton) Click() {
if !b.enabled {
fmt.Printf(" * %s is disabled; click ignored\n", b.caption)
return
}
fmt.Printf(" * %s clicked\n", b.caption)
}
// String は fmt.Stringer を満たす(GUIが無い代わりの状態表示)。
func (b *ColleagueButton) String() string {
return fmt.Sprintf("[%-6s] enabled=%v", b.caption, b.enabled)
}
package main
import "fmt"
// LoginFrame層 (Java版の LoginFrame.java 相当)
// 単体では実行不可。エントリポイントは main.go (go run ./GoF/patterns/Mediator/go)。
// LoginFrame はJava版 LoginFrame.java 相当のConcreteMediator。
// AWTのFrameは持たず、6人のColleagueと調停ロジックだけを保持する。
type LoginFrame struct {
checkGuest *ColleagueCheckbox
checkLogin *ColleagueCheckbox
textUser *ColleagueTextField
textPass *ColleagueTextField
buttonOk *ColleagueButton
buttonCancel *ColleagueButton
}
// NewLoginFrame はコンストラクタ相当。Colleagueを生成し、初期の有効/無効を確定した上で
// 状態を表示する(Java版コンストラクタの createColleagues()→colleagueChanged()→show() に
// 対応。showの代わりにPrintStateでコンソール表示する)。
func NewLoginFrame(title string) *LoginFrame {
fmt.Printf("== %s ==\n", title)
f := &LoginFrame{}
f.CreateColleagues()
f.ColleagueChanged()
f.PrintState()
return f
}
// CreateColleagues はJava版 createColleagues() 相当。Colleagueたちを生成し、
// それぞれにMediator(自分自身)をセットする。
func (f *LoginFrame) CreateColleagues() {
group := &CheckboxGroup{}
f.checkGuest = NewColleagueCheckbox("Guest", group, true)
f.checkLogin = NewColleagueCheckbox("Login", group, false)
f.textUser = NewColleagueTextField("Username")
f.textPass = NewColleagueTextField("Password")
f.buttonOk = NewColleagueButton("OK")
f.buttonCancel = NewColleagueButton("Cancel")
f.checkGuest.SetMediator(f)
f.checkLogin.SetMediator(f)
f.textUser.SetMediator(f)
f.textPass.SetMediator(f)
f.buttonOk.SetMediator(f)
f.buttonCancel.SetMediator(f)
}
// ColleagueChanged はJava版 colleagueChanged() 相当。Colleagueからの通知を受けて
// 各Colleagueの有効/無効を判定する。
func (f *LoginFrame) ColleagueChanged() {
if f.checkGuest.state { // Guest mode
f.textUser.SetColleagueEnabled(false)
f.textPass.SetColleagueEnabled(false)
f.buttonOk.SetColleagueEnabled(true)
} else { // Login mode
f.textUser.SetColleagueEnabled(true)
f.userpassChanged()
}
}
// userpassChanged はJava版 userpassChanged() 相当。textUserまたはtextPassの変更が
// あったときに各Colleagueの有効/無効を判定する。
func (f *LoginFrame) userpassChanged() {
if len(f.textUser.text) > 0 {
f.textPass.SetColleagueEnabled(true)
if len(f.textPass.text) > 0 {
f.buttonOk.SetColleagueEnabled(true)
} else {
f.buttonOk.SetColleagueEnabled(false)
}
} else {
f.textPass.SetColleagueEnabled(false)
f.buttonOk.SetColleagueEnabled(false)
}
}
// PrintState は現在の全Colleagueの状態をコンソールに表示する。GUIが無い代わりの可視化手段。
func (f *LoginFrame) PrintState() {
fmt.Println(f.checkGuest)
fmt.Println(f.checkLogin)
fmt.Println(f.textUser)
fmt.Println(f.textPass)
fmt.Println(f.buttonOk)
fmt.Println(f.buttonCancel)
fmt.Println()
}
package main
import "fmt"
// 実行: go run ./GoF/patterns/Mediator/go
//
// Java版はAWT/SwingのGUI(ユーザー名/パスワード欄、Guest/Loginのラジオボタン、OK/Cancel
// ボタン)を表示して、実際のマウス/キー操作でColleague間の有効/無効が連動する様子を見せる。
// ここではGUIを使わず、各Colleagueへのメソッド呼び出しでユーザー操作をシミュレートし、
// 操作の都度LoginFrame.PrintState()で状態をコンソール出力することで同じ調停ロジックを
// 再現する。
func main() {
frame := NewLoginFrame("Mediator Sample")
fmt.Println("--- Guestモードのままusernameへ入力を試みる(無効なので無視されるはず) ---")
frame.textUser.SetText("alice")
frame.PrintState()
fmt.Println("--- Loginを選択(usernameが有効化されるが、まだ空欄なのでOKは無効のまま) ---")
frame.checkLogin.Select()
frame.PrintState()
fmt.Println("--- 無効なOKボタンを押してみる(無視されるはず) ---")
frame.buttonOk.Click()
fmt.Println()
fmt.Println("--- usernameに入力(passwordが有効化されるが、OKはまだ無効) ---")
frame.textUser.SetText("alice")
frame.PrintState()
fmt.Println("--- passwordに入力(username/passwordとも入力済みになりOKが有効化される) ---")
frame.textPass.SetText("wonderland")
frame.PrintState()
fmt.Println("--- OKボタンを押す ---")
frame.buttonOk.Click()
fmt.Println()
fmt.Println("--- Guestに戻す(username/passwordは無効化されるが入力値は保持される) ---")
frame.checkGuest.Select()
frame.PrintState()
fmt.Println("--- CancelはGuestモードでも常に有効 ---")
frame.buttonCancel.Click()
}
PHP
<?php
ini_set("display_errors", 1);
/**
* 疎結合
* 直接具体的なインスタンス同士を操作させず
* Mediatorを通じて操作させる
* 相手が実際に誰であるか実際は何をしているかは知らせない
* お互いの平和を守る
*/
/**
* 仲介者 Mediator
*/
interface Mediator
{
public function addColleague($assistant);
}
/**
* 同僚
* 仲介役をセットする
* 同じ相談者に対して相談する
*/
interface Colleague
{
public function setMediator($mediator);
public function consult($to, $message);
}
class Hero implements Mediator
{
private $colleagues = array();
/**
* 管轄下に置かれる部下を追加
*/
public function addColleague($assistant)
{
$this->colleagues[$assistant->getName()] = $assistant;
$assistant->setMediator($this);
}
/**
* from部下からto部下へ相談された内容を伝える
* @param $from 部下
* @param $to $部下
* @param $message 相談内容
*/
public function sendMessage($from, $to, $message)
{
if (array_key_exists($to, $this->colleagues)) {
print $from->getName() . "から" . $this->colleagues[$to]->getName() . "へ";
$this->colleagues[$to]->receiveMessage($message);
} else {
print "知らない部下です<br>";
}
}
}
/**
* アシスタント
* 他の部下に相談する前に一度仲介役を介す
*/
class Assistant implements Colleague
{
private $name;
private $mediator;
public function __construct($name)
{
$this->name = $name;
}
/**
* 何か相談するときに報告する仲介役の設定
*/
public function setMediator($mediator)
{
$this->mediator = $mediator;
}
public function getName()
{
return $this->name;
}
/**
* $toのぶかに対して相談するが
* 自分は誰々に相談したいと、仲介役を中継
*/
public function consult($to, $message)
{
$this->mediator->sendMessage($this, $to, $message);
}
/**
* 相談内容を受け取る
*/
public function receiveMessage($message)
{
print "[$message] <br>";
}
}
$hero = new Hero();
$yellow = new Assistant("yellow");
$blue = new Assistant("blue");
$hero->addColleague($yellow);
$hero->addColleague($blue);
$blue->consult("yellow", "how are you? ");
$yellow->consult("blue", "soso.");
TypeScript
Go版と同じ「GUIをテキストベースの状態機械シミュレーションに置き換える」方針。ColleagueはJava版と同じくinterfaceとして定義しつつ、3クラス共通のsetMediator()実装だけはabstract class BaseColleagueにまとめている(ChainOfResponsibility版TSのSupportと同じ方針)。Guest/LoginのラジオボタンはCheckboxGroupで排他選択を再現し、無効化されたテキストフィールド/ボタンへの入力・クリックはGUIと同様に無視される。
実行: npx tsx GoF/patterns/Mediator/typescript/main.ts
// Mediator パターン: Mediator層 (Java版の Mediator.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
export interface Mediator {
createColleagues(): void;
colleagueChanged(): void;
}
// Mediator パターン: Colleague層 (Java版の Colleague.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版のColleagueはただのinterfaceで、3つの具象クラス(ColleagueCheckbox/
// ColleagueButton/ColleagueTextField)はいずれもsetMediator()を「mediatorフィールドへ
// 代入するだけ」の同じ一行実装で持っている(Javaのinterfaceは実装を持てないため、
// 3クラスがこの1行を重複して書いている)。TypeScriptのabstractクラスならこの1行を
// 1箇所にまとめられるので、ChainOfResponsibility版TS実装のSupportと同じ方針で
// BaseColleagueにまとめる。
import { Mediator } from "./mediator";
export interface Colleague {
setMediator(mediator: Mediator): void;
setColleagueEnabled(enabled: boolean): void;
}
export abstract class BaseColleague implements Colleague {
protected mediator!: Mediator;
setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
abstract setColleagueEnabled(enabled: boolean): void;
}
// ColleagueCheckbox層 (Java版の ColleagueCheckbox.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
import { BaseColleague } from "./colleague";
// CheckboxGroup はJava版 java.awt.CheckboxGroup 相当。同じグループに属する
// ColleagueCheckboxはラジオボタンとして排他選択される(1つを選ぶと他は自動的に外れる)。
export class CheckboxGroup {
selected: ColleagueCheckbox | null = null;
}
// ColleagueCheckbox はJava版 ColleagueCheckbox.java 相当のConcreteColleague。
// Guest/Loginのラジオボタンとして使われる。
export class ColleagueCheckbox extends BaseColleague {
private state: boolean; // 選択されているか(Java版 Checkbox.getState() 相当)
// コンストラクタ相当。java.awt.Checkbox(caption, group, state)に対応。
constructor(
private readonly caption: string,
private readonly group: CheckboxGroup,
state: boolean,
) {
super();
this.state = state;
if (state) {
group.selected = this;
}
}
get checked(): boolean {
return this.state;
}
// Java版サンプルではGuest/Loginのラジオボタン自体が無効化されることは無いが、
// Colleagueインタフェースを満たすために実装しておく(no-op)。
setColleagueEnabled(_enabled: boolean): void {
// no-op
}
// ラジオボタンをクリックした操作をシミュレートする(Java版 itemStateChanged 相当)。
// 既に選択済みのボタンを選んでもAWTのCheckboxGroupはItemEventを発火しないため、
// ここでも同じ挙動にする。
select(): void {
if (this.group.selected === this) {
return;
}
if (this.group.selected !== null) {
this.group.selected.state = false;
}
this.group.selected = this;
this.state = true;
this.mediator.colleagueChanged();
}
// GUIが無い代わりの状態表示。
toString(): string {
return `[${this.caption.padEnd(5)}] checked=${this.state}`;
}
}
// ColleagueTextField層 (Java版の ColleagueTextField.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
import { BaseColleague } from "./colleague";
// ColleagueTextField はJava版 ColleagueTextField.java 相当のConcreteColleague。
// Username/Passwordの入力欄として使われる。
export class ColleagueTextField extends BaseColleague {
private text = "";
private enabled = true;
// label はjava.awt.TextFieldには無い、コンソール表示用の追加情報(Username/Password)。
constructor(private readonly label: string) {
super();
}
get value(): string {
return this.text;
}
// Java版は合わせて背景色もwhite/lightGrayに切り替えるが、GUIが無いのでここでは状態のみ。
setColleagueEnabled(enabled: boolean): void {
this.enabled = enabled;
}
// テキスト入力操作をシミュレートする(Java版 textValueChanged 相当)。
// 無効化されている間はSwingのTextField同様キー入力を受け付けないので、入力を無視する。
setText(text: string): void {
if (!this.enabled) {
console.log(` * ${this.label} is disabled; input "${text}" ignored`);
return;
}
this.text = text;
this.mediator.colleagueChanged();
}
// GUIが無い代わりの状態表示。
toString(): string {
return `[${this.label.padEnd(8)}] enabled=${String(this.enabled).padEnd(5)} text="${this.text}"`;
}
}
// ColleagueButton層 (Java版の ColleagueButton.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
import { BaseColleague } from "./colleague";
// ColleagueButton はJava版 ColleagueButton.java 相当のConcreteColleague。OK/Cancelボタン。
export class ColleagueButton extends BaseColleague {
private enabled = true;
constructor(private readonly caption: string) {
super();
}
setColleagueEnabled(enabled: boolean): void {
this.enabled = enabled;
}
// ボタン押下をシミュレートする(Java版 actionPerformed 相当。ただしJava版はOK/Cancel
// どちらを押してもSystem.exit(0)するが、ここではexitさせずログ出力だけに留める)。
click(): void {
if (!this.enabled) {
console.log(` * ${this.caption} is disabled; click ignored`);
return;
}
console.log(` * ${this.caption} clicked`);
}
// GUIが無い代わりの状態表示。
toString(): string {
return `[${this.caption.padEnd(6)}] enabled=${this.enabled}`;
}
}
// LoginFrame層 (Java版の LoginFrame.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
import { Mediator } from "./mediator";
import { CheckboxGroup, ColleagueCheckbox } from "./colleague_checkbox";
import { ColleagueTextField } from "./colleague_text_field";
import { ColleagueButton } from "./colleague_button";
// LoginFrame はJava版 LoginFrame.java 相当のConcreteMediator。
// AWTのFrameは持たず、6人のColleagueと調停ロジックだけを保持する。
export class LoginFrame implements Mediator {
checkGuest!: ColleagueCheckbox;
checkLogin!: ColleagueCheckbox;
textUser!: ColleagueTextField;
textPass!: ColleagueTextField;
buttonOk!: ColleagueButton;
buttonCancel!: ColleagueButton;
// コンストラクタ。Colleagueを生成し、初期の有効/無効を確定した上で状態を表示する
// (Java版コンストラクタの createColleagues()→colleagueChanged()→show() に対応。
// showの代わりにprintStateでコンソール表示する)。
constructor(title: string) {
console.log(`== ${title} ==`);
this.createColleagues();
this.colleagueChanged();
this.printState();
}
// Java版 createColleagues() 相当。Colleagueたちを生成し、それぞれにMediator(自分自身)を
// セットする。
createColleagues(): void {
const group = new CheckboxGroup();
this.checkGuest = new ColleagueCheckbox("Guest", group, true);
this.checkLogin = new ColleagueCheckbox("Login", group, false);
this.textUser = new ColleagueTextField("Username");
this.textPass = new ColleagueTextField("Password");
this.buttonOk = new ColleagueButton("OK");
this.buttonCancel = new ColleagueButton("Cancel");
this.checkGuest.setMediator(this);
this.checkLogin.setMediator(this);
this.textUser.setMediator(this);
this.textPass.setMediator(this);
this.buttonOk.setMediator(this);
this.buttonCancel.setMediator(this);
}
// Java版 colleagueChanged() 相当。Colleagueからの通知を受けて各Colleagueの有効/無効を
// 判定する。
colleagueChanged(): void {
if (this.checkGuest.checked) {
// Guest mode
this.textUser.setColleagueEnabled(false);
this.textPass.setColleagueEnabled(false);
this.buttonOk.setColleagueEnabled(true);
} else {
// Login mode
this.textUser.setColleagueEnabled(true);
this.userpassChanged();
}
}
// Java版 userpassChanged() 相当。textUserまたはtextPassの変更があったときに
// 各Colleagueの有効/無効を判定する。
private userpassChanged(): void {
if (this.textUser.value.length > 0) {
this.textPass.setColleagueEnabled(true);
if (this.textPass.value.length > 0) {
this.buttonOk.setColleagueEnabled(true);
} else {
this.buttonOk.setColleagueEnabled(false);
}
} else {
this.textPass.setColleagueEnabled(false);
this.buttonOk.setColleagueEnabled(false);
}
}
// 現在の全Colleagueの状態をコンソールに表示する。GUIが無い代わりの可視化手段。
printState(): void {
console.log(this.checkGuest.toString());
console.log(this.checkLogin.toString());
console.log(this.textUser.toString());
console.log(this.textPass.toString());
console.log(this.buttonOk.toString());
console.log(this.buttonCancel.toString());
console.log("");
}
}
// 実行: npx tsx GoF/patterns/Mediator/typescript/main.ts
//
// Java版はAWT/SwingのGUI(ユーザー名/パスワード欄、Guest/Loginのラジオボタン、OK/Cancel
// ボタン)を表示して、実際のマウス/キー操作でColleague間の有効/無効が連動する様子を見せる。
// ここではGUIを使わず、各Colleagueへのメソッド呼び出しでユーザー操作をシミュレートし、
// 操作の都度LoginFrame.printState()で状態をコンソール出力することで同じ調停ロジックを
// 再現する。
import { LoginFrame } from "./login_frame";
function main(): void {
const frame = new LoginFrame("Mediator Sample");
console.log("--- Guestモードのままusernameへ入力を試みる(無効なので無視されるはず) ---");
frame.textUser.setText("alice");
frame.printState();
console.log("--- Loginを選択(usernameが有効化されるが、まだ空欄なのでOKは無効のまま) ---");
frame.checkLogin.select();
frame.printState();
console.log("--- 無効なOKボタンを押してみる(無視されるはず) ---");
frame.buttonOk.click();
console.log("");
console.log("--- usernameに入力(passwordが有効化されるが、OKはまだ無効) ---");
frame.textUser.setText("alice");
frame.printState();
console.log("--- passwordに入力(username/passwordとも入力済みになりOKが有効化される) ---");
frame.textPass.setText("wonderland");
frame.printState();
console.log("--- OKボタンを押す ---");
frame.buttonOk.click();
console.log("");
console.log("--- Guestに戻す(username/passwordは無効化されるが入力値は保持される) ---");
frame.checkGuest.select();
frame.printState();
console.log("--- CancelはGuestモードでも常に有効 ---");
frame.buttonCancel.click();
}
main();
Python
Go/TypeScript版と同じ方針。Colleague(ABC)を抽象基底クラスとし、set_mediator()の共通実装をBaseColleagueにまとめる(ChainOfResponsibility版PythonのSupportと同じ方針)。main.pyでは、Guestモードのまま入力を試みて無視されることを確認 → Loginを選択 → username/passwordを入力してOKが有効化されることを確認 → クリック → Guestに戻して再度無効化される、という一連の操作シーケンスを流し、その都度LoginFrame.print_state()で全Colleagueの状態を出力する。
実行: python3 GoF/patterns/Mediator/python/main.py
"""Mediator パターン: Mediator層 (Java版の Mediator.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
class Mediator(ABC):
@abstractmethod
def create_colleagues(self) -> None: ...
@abstractmethod
def colleague_changed(self) -> None: ...
"""Mediator パターン: Colleague層 (Java版の Colleague.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
Java版のColleagueはただのinterfaceで、3つの具象クラス(ColleagueCheckbox/
ColleagueButton/ColleagueTextField)はいずれもset_mediator()を「mediatorフィールドへ
代入するだけ」の同じ一行実装で持っている(Javaのinterfaceは実装を持てないため、3クラスが
この1行を重複して書いている)。ChainOfResponsibility版PythonのSupportと同じ方針で、
抽象基底クラスBaseColleagueにこの1行をまとめる。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from mediator import Mediator
class Colleague(ABC):
@abstractmethod
def set_mediator(self, mediator: Mediator) -> None: ...
@abstractmethod
def set_colleague_enabled(self, enabled: bool) -> None: ...
class BaseColleague(Colleague):
def __init__(self) -> None:
self._mediator: Mediator | None = None
def set_mediator(self, mediator: Mediator) -> None:
self._mediator = mediator
@abstractmethod
def set_colleague_enabled(self, enabled: bool) -> None: ...
"""ColleagueCheckbox層 (Java版の ColleagueCheckbox.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from colleague import BaseColleague
class CheckboxGroup:
"""Java版 java.awt.CheckboxGroup 相当。同じグループに属するColleagueCheckboxは
ラジオボタンとして排他選択される(1つを選ぶと他は自動的に外れる)。
"""
def __init__(self) -> None:
self.selected: ColleagueCheckbox | None = None
class ColleagueCheckbox(BaseColleague):
"""Java版 ColleagueCheckbox.java 相当のConcreteColleague。
Guest/Loginのラジオボタンとして使われる。
"""
def __init__(self, caption: str, group: CheckboxGroup, state: bool) -> None:
# コンストラクタ相当。java.awt.Checkbox(caption, group, state)に対応。
super().__init__()
self._caption = caption
self._group = group
self._state = state # 選択されているか(Java版 Checkbox.getState() 相当)
if state:
group.selected = self
@property
def checked(self) -> bool:
return self._state
def set_colleague_enabled(self, enabled: bool) -> None:
"""Java版サンプルではGuest/Loginのラジオボタン自体が無効化されることは無いが、
Colleagueインタフェースを満たすために実装しておく(no-op)。
"""
def select(self) -> None:
"""ラジオボタンをクリックした操作をシミュレートする(Java版 itemStateChanged 相当)。
既に選択済みのボタンを選んでもAWTのCheckboxGroupはItemEventを発火しないため、
ここでも同じ挙動にする。
"""
if self._group.selected is self:
return
if self._group.selected is not None:
self._group.selected._state = False
self._group.selected = self
self._state = True
assert self._mediator is not None
self._mediator.colleague_changed()
def __str__(self) -> str:
"""GUIが無い代わりの状態表示。"""
return f"[{self._caption:<5}] checked={self._state}"
"""ColleagueTextField層 (Java版の ColleagueTextField.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from colleague import BaseColleague
class ColleagueTextField(BaseColleague):
"""Java版 ColleagueTextField.java 相当のConcreteColleague。
Username/Passwordの入力欄として使われる。
"""
def __init__(self, label: str) -> None:
# label はjava.awt.TextFieldには無い、コンソール表示用の追加情報(Username/Password)。
super().__init__()
self._label = label
self._text = ""
self._enabled = True
@property
def value(self) -> str:
return self._text
def set_colleague_enabled(self, enabled: bool) -> None:
"""Java版は合わせて背景色もwhite/lightGrayに切り替えるが、GUIが無いのでここでは
状態のみ。
"""
self._enabled = enabled
def set_text(self, text: str) -> None:
"""テキスト入力操作をシミュレートする(Java版 textValueChanged 相当)。
無効化されている間はSwingのTextField同様キー入力を受け付けないので、入力を無視する。
"""
if not self._enabled:
print(f' * {self._label} is disabled; input "{text}" ignored')
return
self._text = text
assert self._mediator is not None
self._mediator.colleague_changed()
def __str__(self) -> str:
"""GUIが無い代わりの状態表示。"""
return f'[{self._label:<8}] enabled={str(self._enabled):<5} text="{self._text}"'
"""ColleagueButton層 (Java版の ColleagueButton.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from colleague import BaseColleague
class ColleagueButton(BaseColleague):
"""Java版 ColleagueButton.java 相当のConcreteColleague。OK/Cancelボタン。"""
def __init__(self, caption: str) -> None:
super().__init__()
self._caption = caption
self._enabled = True
def set_colleague_enabled(self, enabled: bool) -> None:
self._enabled = enabled
def click(self) -> None:
"""ボタン押下をシミュレートする(Java版 actionPerformed 相当。ただしJava版はOK/Cancel
どちらを押してもsys.exit(0)するが、ここではexitさせずログ出力だけに留める)。
"""
if not self._enabled:
print(f" * {self._caption} is disabled; click ignored")
return
print(f" * {self._caption} clicked")
def __str__(self) -> str:
"""GUIが無い代わりの状態表示。"""
return f"[{self._caption:<6}] enabled={self._enabled}"
"""LoginFrame層 (Java版の LoginFrame.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from colleague_button import ColleagueButton
from colleague_checkbox import CheckboxGroup, ColleagueCheckbox
from colleague_text_field import ColleagueTextField
from mediator import Mediator
class LoginFrame(Mediator):
"""Java版 LoginFrame.java 相当のConcreteMediator。
AWTのFrameは持たず、6人のColleagueと調停ロジックだけを保持する。
"""
def __init__(self, title: str) -> None:
# コンストラクタ。Colleagueを生成し、初期の有効/無効を確定した上で状態を表示する
# (Java版コンストラクタの createColleagues()→colleagueChanged()→show() に対応。
# showの代わりにprint_stateでコンソール表示する)。
print(f"== {title} ==")
self.check_guest: ColleagueCheckbox
self.check_login: ColleagueCheckbox
self.text_user: ColleagueTextField
self.text_pass: ColleagueTextField
self.button_ok: ColleagueButton
self.button_cancel: ColleagueButton
self.create_colleagues()
self.colleague_changed()
self.print_state()
def create_colleagues(self) -> None:
"""Java版 create_colleagues() 相当。Colleagueたちを生成し、それぞれにMediator
(自分自身)をセットする。
"""
group = CheckboxGroup()
self.check_guest = ColleagueCheckbox("Guest", group, True)
self.check_login = ColleagueCheckbox("Login", group, False)
self.text_user = ColleagueTextField("Username")
self.text_pass = ColleagueTextField("Password")
self.button_ok = ColleagueButton("OK")
self.button_cancel = ColleagueButton("Cancel")
self.check_guest.set_mediator(self)
self.check_login.set_mediator(self)
self.text_user.set_mediator(self)
self.text_pass.set_mediator(self)
self.button_ok.set_mediator(self)
self.button_cancel.set_mediator(self)
def colleague_changed(self) -> None:
"""Java版 colleagueChanged() 相当。Colleagueからの通知を受けて各Colleagueの
有効/無効を判定する。
"""
if self.check_guest.checked: # Guest mode
self.text_user.set_colleague_enabled(False)
self.text_pass.set_colleague_enabled(False)
self.button_ok.set_colleague_enabled(True)
else: # Login mode
self.text_user.set_colleague_enabled(True)
self._userpass_changed()
def _userpass_changed(self) -> None:
"""Java版 userpassChanged() 相当。text_userまたはtext_passの変更があったときに
各Colleagueの有効/無効を判定する。
"""
if len(self.text_user.value) > 0:
self.text_pass.set_colleague_enabled(True)
if len(self.text_pass.value) > 0:
self.button_ok.set_colleague_enabled(True)
else:
self.button_ok.set_colleague_enabled(False)
else:
self.text_pass.set_colleague_enabled(False)
self.button_ok.set_colleague_enabled(False)
def print_state(self) -> None:
"""現在の全Colleagueの状態をコンソールに表示する。GUIが無い代わりの可視化手段。"""
print(self.check_guest)
print(self.check_login)
print(self.text_user)
print(self.text_pass)
print(self.button_ok)
print(self.button_cancel)
print()
"""Mediator パターン: LoginFrameの状態遷移シミュレーション (Java版Main.javaと同じお題)
実行: python3 main.py
(もしくはリポジトリルートから python3 GoF/patterns/Mediator/python/main.py)
Java版はAWT/SwingのGUI(ユーザー名/パスワード欄、Guest/Loginのラジオボタン、OK/Cancel
ボタン)を表示して、実際のマウス/キー操作でColleague間の有効/無効が連動する様子を見せる。
ここではGUIを使わず、各Colleagueへのメソッド呼び出しでユーザー操作をシミュレートし、
操作の都度LoginFrame.print_state()で状態をコンソール出力することで同じ調停ロジックを
再現する。
"""
from __future__ import annotations
from login_frame import LoginFrame
def main() -> None:
frame = LoginFrame("Mediator Sample")
print("--- Guestモードのままusernameへ入力を試みる(無効なので無視されるはず) ---")
frame.text_user.set_text("alice")
frame.print_state()
print("--- Loginを選択(usernameが有効化されるが、まだ空欄なのでOKは無効のまま) ---")
frame.check_login.select()
frame.print_state()
print("--- 無効なOKボタンを押してみる(無視されるはず) ---")
frame.button_ok.click()
print()
print("--- usernameに入力(passwordが有効化されるが、OKはまだ無効) ---")
frame.text_user.set_text("alice")
frame.print_state()
print("--- passwordに入力(username/passwordとも入力済みになりOKが有効化される) ---")
frame.text_pass.set_text("wonderland")
frame.print_state()
print("--- OKボタンを押す ---")
frame.button_ok.click()
print()
print("--- Guestに戻す(username/passwordは無効化されるが入力値は保持される) ---")
frame.check_guest.select()
frame.print_state()
print("--- CancelはGuestモードでも常に有効 ---")
frame.button_cancel.click()
if __name__ == "__main__":
main()