デザインパターンMediatorについて勉強した

mediatorパターンとは

mediatorは仲介人の意味です。
あっちの人とそっちの人が、さらにこっちの人が自分勝手にお互いに指示を出し合っていては混乱が起こります。
それでは全員の考えをまとめて最適な指示を出すリーダーを一人用意しようというのがこのmediatorパターンです。

主にGUIを使ったプログラムに用いられるパターンのようです。

今回は簡単なパズルゲームを作ってみます。
8*8のパネルが並んでおり、そのうち一つのパネルをクリックするとクリックしたパネルとその4方のパネルがon/offでき、全てのパネルをonにするとクリアというあのゲームです。

コード例

Colleagueインタフェース

public interface Colleague {

	void setMediator(Mediator mediator);

	void setPosition(int x,int y);

	void setSelected(boolean select);

	boolean getSelected();
}

「あの人」や「この人」を表すインタフェースです。
mediatorパターンとして必要なのはsetMediatorメソッドただ一つです。
setMediatorメソッドは仲介人を保持するためのメソッドで、自身に変化があった場合は何も考えずにとりあえずmediatorに自身の変化を連絡します。
mediator側は連絡を受けると全体の状態を考慮して、残りのメソッド(ここではsetPosition、setSelected、getSelected)を駆使して各Colleagueに指示を出します。

setPositionメソッドはこの例固有のメソッドです。
パネルが8*8ありますが、自身がどこに位置しているのかを教えてもらうためのセッターです。

setSelectedメソッドはこの例固有のメソッドです。
クリックされた周囲のパネルはon/offを切り替える必要がありますが、このon/offを指示するのがこのメソッドです。

getSelectedメソッドはこの例固有のメソッドです。
現在のon/off状態を返します。

Mediatorインタフェース

public interface Mediator {

	void operating(int x,int y);
}

仲介役を表すインタフェースです。
Colleagueは何か変化があった場合、「何か変化がありました」と仲介役に連絡する必要があります。
このメソッドはその連絡用です。
引数は自身の位置、つまり右上のパネルなのか中央のパネルなのかの情報です。
この引数もこの例固有のもので、引数がいらない場合や文字列を引数とする場合、operating2,operating3のように複数種類の連絡用メソッドを作ることもできます。

実装

Cellクラス

public class Cell extends JLabel implements Colleague {

	private Mediator mediator ;
	private boolean select;
	private int x;
	private int y;

	public Cell(Mediator mediator,int x,int y) {
		setMediator(mediator);
		setPosition(x,y);
		addMouseListener(
				new MouseListener() {

					@Override
					public void mouseClicked(MouseEvent e) {
						Cell.this.mediator.operating(x,y);
					}

					@Override
					public void mousePressed(MouseEvent e) {}

					@Override
					public void mouseReleased(MouseEvent e) {}

					@Override
					public void mouseEntered(MouseEvent e) {}

					@Override
					public void mouseExited(MouseEvent e) {}
				});
		setPreferredSize(new Dimension(50,50));
		setHorizontalTextPosition(JLabel.CENTER);
		setSelected(false);
	}

	@Override
	public void setMediator(Mediator mediator) {
		this.mediator = mediator;
	}

	@Override
	public void setSelected(boolean select) {
		this.select = select;
		if(select) {
			setBackground(Color.WHITE);
			setText("O");
		}else {
			setBackground(Color.LIGHT_GRAY);
			setText("X");
		}
	}

	@Override
	public boolean getSelected() {
		return select;
	}

	@Override
	public void setPosition(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

Colleagueインタフェースを実装したクラスです。
medoatorフィールドは仲介役を保持、selectフィールドは現在のon/off状態を、x,yフィールドは自身のパネル座標をそれぞれ保持しておきます。

コンストラクタではMediatorと座標を受け取り、保持しています。
またパネルがクリックされたという情報が欲しいので、MouseListenerからクリックイベントを受け取っています。
パネルのサイズ、文字の位置、そしてデフォルトのon/off状態を設定しています。

setMediatorメソッドではmediatorを保持しています。

setSelectedメソッドではon/off状態を保持します。
それと同時に背景色とO/Xを表示します。

getSelectedメソッドは現在のon/off状態を返します。

setPositionメソッドはこのパネル自身の位置を保持します。

GamePanelクラス

public class GamePanel extends JPanel implements Mediator {

	private Colleague[][] cells ;
	private int width = 8;
	private int height = 8;

	public GamePanel() {
		setLayout(new GridLayout(width,height));

		cells = new Colleague[width][height];
		for(int i=0;i<cells.length;i++) {
			for(int j=0;j<cells[i].length;j++) {
				Cell cell = new Cell(this,i,j);
				cells[i][j] = cell;
				add(cell);
			}
		}
	}

	@Override
	public void operating(int x,int y) {
		turnover(x,y);
		turnover(x-1,y);
		turnover(x+1,y);
		turnover(x,y-1);
		turnover(x,y+1);
	}

	private void turnover(int x,int y) {
		if(x<0 || width<=x) return;
		if(y<0 || height<=y) return;
		cells[x][y].setSelected(!cells[x][y].getSelected());
	}
}

仲介役です。
仲介役はそれぞれのColleague達の状態を見てそれぞれに何をすべきかを指示していきます。
cellsフィールドはColleague達を保持するフィールドです。
while,heightフィールドはパネルが8*8であることを設定します。
コンストラクタでは8*8個のパネルを配置していきます。

operatingメソッドはColleagueから受ける連絡用メソッドです。
このメソッドが呼ばれると、引数の座標を中心に、4方向のパネルのon/offを反転します。

実際に反転できるか(パネルの範囲外かどうか)をチェックするのはturnoverメソッドの仕事です。

実行

実際に動かしてみましょう。
とは言ってもまだウィンドウを作っていないので、まずはそこからです。

DefaultFrameクラス

public class DefaultFrame extends JFrame implements WindowListener{

	public DefaultFrame() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		addWindowListener(this);
	}

	@Override
	public void windowOpened(WindowEvent e) {
		pack();
		setLocationRelativeTo(null);
	}

	@Override
	public void windowClosing(WindowEvent e) {}

	@Override
	public void windowClosed(WindowEvent e) {}

	@Override
	public void windowIconified(WindowEvent e) {}

	@Override
	public void windowDeiconified(WindowEvent e) {}

	@Override
	public void windowActivated(WindowEvent e) {}

	@Override
	public void windowDeactivated(WindowEvent e) {}
}

簡単なコードなので説明は省略します。

public class Main {

	public Main() {
		JFrame frame = new DefaultFrame();
		frame.add(new GamePanel());

		frame.setVisible(true);
	}

	public static void main(String[] args) {
		new Main();
	}
}

f:id:chiopino:20210622220819p:plain

特徴

主にGUIなどで、あちらこちらかでマウスやキーボードによる外部入力があるときに活躍するパターンです。
さらに複雑なプログラムだと仲介役の仲介役などが出てきたりしそうですね。

余談

最後の実行結果のスクリーンショットを見て何か思いませんか?
そう、cellのon/offに連動して色がつくはずでしたが、このスクリーンショットには色がついていません。
今までの経験則ですが、どうやらmacではバックグラウンドカラーというものが設定できないようでして、windowsで同じプログラムを実行すると色付きで表示されます。(このプログラムでは試していませんがおそらくそうなるでしょう)
こういう簡単なゲームを作る(実行する)にはmacは向いていないのかもしれません。