最近のフルタイムは、運用改善おじさんです。
デザインパターンを勉強し直しています。
Mediatorパターンの理解を深めるために、
サンプルコードとして、N番煎じかと思いますが…川渡りパズルをコードにしました。
川渡りパズルの解答を割り出すプログラムではなく、
川渡りパズルをシミュレートするプログラムです。
Mediatorについて、認識の誤りがありましたら、
もし良ければ、ご指摘頂けると幸いです。
環境
- MacBook Pro (Retina 13-inch、Early 2015)
- OS X El Capitan 10.11.6
- Java 1.8.0_102
- Eclipse Mars.2 Release (4.5.2)
川渡りパズル
かの有名なアレです。
こちらの岸から、船を使って、向こうの岸へ渡るアレです。
登場キャラクターは、色々あるかと思いますが、
今回は、農夫、オオカミ、ヤギ、キャベツにしました。
船を漕げるのは農夫のみ。
船に乗れるのは、農夫と、もう1キャラクターのみ。
同じ岸に農夫がいないときに、オオカミ、ヤギを残してしまうと、オオカミはヤギを食べてしまう。
同じ岸に農夫がいないときに、ヤギ、キャベツを残してしまうと、ヤギはキャベツを食べてしまう。
すべてを無事に、向こう岸に渡るには、どうしたらよいのか? というアレです。
前提
Farmer.java, Chabbage.java, Goat.java, Wolf.javaは、
以下を継承しています。
public abstract class CharacterBase implements Character {
private Position position;
private Status status;
public CharacterBase() {
this.position = Position.FRONT;
this.status = Status.ALIVE;
}
@Override
public Status getStatus() {
return this.status;
}
@Override
public void setStatus(Status status) {
this.status = status;
}
@Override
public Position getPosition() {
return this.position;
}
@Override
public void setPosition(Position position) {
this.position = position;
}
@Override
public void cross() {
switch (this.getPosition()) {
case FRONT:
this.setPosition(Position.BEYOND);
break;
case BEYOND:
this.setPosition(Position.FRONT);
break;
default:
break;
}
}
}
また、Farmer.javaのみ、
cross(Character withCharacter)メソッドを実装しています。
public class Farmer extends CharacterBase {
public void cross(Character withCharacter) {
if (withCharacter == null) {
throw new IllegalArgumentException("一緒に渡るキャラクターを指定して下さい。");
}
if (withCharacter instanceof Farmer) {
throw new IllegalArgumentException("農夫を指定することはできません。");
}
if (!this.getPosition().equals(withCharacter.getPosition())) {
throw new IllegalArgumentException("同じ位置にいるキャラクターのみ、一緒に渡ることができます。");
}
this.cross();
withCharacter.cross();
}
}
Mediatorがいない場合
各キャラクターを全て知っているクラスが居ないため、
実行クラス内で、逐一状況を確認し、
キャラクターのステータスを更新しなければならなくなってしまいました。
public class CrossRiverExecutor {
private Farmer farmer;
private Wolf wolf;
private Goat goat;
private Cabbage cabbage;
public CrossRiverExecutor(Farmer farmer, Wolf wolf, Goat goat, Cabbage cabbage) {
this.farmer = farmer;
this.wolf = wolf;
this.goat = goat;
this.cabbage = cabbage;
}
public void execute() {
farmer.cross(goat);
// こちらの岸:オオカミ、キャベツ
// 向こう岸:農夫、ヤギ
updateStatus();
farmer.cross();
// こちらの岸:農夫、オオカミ、キャベツ
// 向こう岸:ヤギ
updateStatus();
farmer.cross(cabbage);
// こちらの岸:オオカミ
// 向こう岸:農夫、ヤギ、キャベツ
updateStatus();
farmer.cross(goat);
// こちらの岸:農夫、ヤギ、オオカミ
// 向こう岸:キャベツ
updateStatus();
farmer.cross(wolf);
// こちらの岸:ヤギ
// 向こう岸:農夫、オオカミ、キャベツ
updateStatus();
farmer.cross();
// こちらの岸:農夫、ヤギ
// 向こう岸:オオカミ、キャベツ
updateStatus();
farmer.cross(goat);
// こちらの岸:
// 向こう岸:農夫、オオカミ、ヤギ、キャベツ
updateStatus();
}
private void updateStatus() {
if (!farmer.getPosition().equals(goat.getPosition()) && wolf.getPosition().equals(goat.getPosition())) {
goat.setStatus(Status.DEAD);
}
if (!farmer.getPosition().equals(cabbage.getPosition()) && cabbage.getPosition().equals(goat.getPosition())) {
cabbage.setStatus(Status.DEAD);
}
}
}
これでも、川渡りパズルを表現できているとは思いますが、
読みにくく、また、updateStatusメソッドをテストしにくい状態になってしまっています。
あまり良いコードの状態とはいえないと思います。
Mediatorがいる場合
ここで、キャラクター全体を知る、Mediatorを用意します。
public class CrossRiverMediator implements CrossRiver {
private Farmer farmer;
private Wolf wolf;
private Goat goat;
private Cabbage cabbage;
public CrossRiverMediator(Farmer farmer, Wolf wolf, Goat goat, Cabbage cabbage) {
this.farmer = farmer;
this.wolf = wolf;
this.goat = goat;
this.cabbage = cabbage;
}
@Override
public void cross(Character character) {
switch (character.getPosition()) {
case FRONT:
character.setPosition(Position.BEYOND);
break;
case BEYOND:
character.setPosition(Position.FRONT);
break;
default:
break;
}
}
@Override
public void cross(Farmer character, Character withCharacter) {
if (withCharacter == null) {
throw new IllegalArgumentException("一緒に渡るキャラクターを指定して下さい。");
}
if (withCharacter instanceof Farmer) {
throw new IllegalArgumentException("農夫を指定することはできません。");
}
if (!character.getPosition().equals(withCharacter.getPosition())) {
throw new IllegalArgumentException("同じ位置にいるキャラクターのみ、一緒に渡ることができます。");
}
character.cross();
withCharacter.cross();
updateStatus();
}
private void updateStatus() {
if (!farmer.getPosition().equals(goat.getPosition()) && wolf.getPosition().equals(goat.getPosition())) {
goat.setStatus(Status.DEAD);
}
if (!farmer.getPosition().equals(cabbage.getPosition()) && cabbage.getPosition().equals(goat.getPosition())) {
cabbage.setStatus(Status.DEAD);
}
}
}
キャラクター全員を知っているため、
各キャラクターの生死を更新する、updateStatusメソッドを実行することができます。
また、それに伴い、
各キャラクターの親クラスである、CharacterBase.javaと、
Farmer.javaかのcross()メソッド、cross(Character)メソッドを、
Mediator経由に実行するように変更します。
public abstract class CharacterBase implements Character {
protected CrossRiver crossRiver;
private Position position;
private Status status;
public CharacterBase() {
this.position = Position.FRONT;
this.status = Status.ALIVE;
}
@Override
public void setMediator(CrossRiver crossRiver) {
this.crossRiver = crossRiver;
}
@Override
public Status getStatus() {
return this.status;
}
@Override
public void setStatus(Status status) {
this.status = status;
}
@Override
public Position getPosition() {
return this.position;
}
@Override
public void setPosition(Position position) {
this.position = position;
}
@Override
public void cross() {
this.crossRiver.cross(this);
}
}
public class Farmer extends CharacterBase {
public void cross(Character withCharacter) {
this.crossRiver.cross(this, withCharacter);
}
}
そうすると、実行クラスは、
以下のようになります。
public class CrossRiverExecutor {
private Farmer farmer;
private Wolf wolf;
private Goat goat;
private Cabbage cabbage;
private CrossRiverMediator crossRiver;
public CrossRiverExecutor() {
farmer = new Farmer();
wolf = new Wolf();
goat = new Goat();
cabbage = new Cabbage();
crossRiver = new CrossRiverMediator(farmer, wolf, goat, cabbage);
farmer.setMediator(crossRiver);
wolf.setMediator(crossRiver);
goat.setMediator(crossRiver);
cabbage.setMediator(crossRiver);
}
public void execute() {
farmer.cross(goat); // こちらの岸:オオカミ、キャベツ 向こう岸:農夫、ヤギ
farmer.cross(); // こちらの岸:農夫、オオカミ、キャベツ 向こう岸:ヤギ
farmer.cross(cabbage); // こちらの岸:オオカミ 向こう岸:農夫、ヤギ、キャベツ
farmer.cross(goat); // こちらの岸:農夫、ヤギ、オオカミ 向こう岸:キャベツ
farmer.cross(wolf); // こちらの岸:ヤギ 向こう岸:農夫、オオカミ、キャベツ
farmer.cross(); // こちらの岸:農夫、ヤギ 向こう岸:オオカミ、キャベツ
farmer.cross(goat); // こちらの岸: 向こう岸:農夫、オオカミ、ヤギ、キャベツ
}
}
よって、実行クラスからロジックを切り離すことができ、
ロジックをテストする際は、Mediatorをテストすればよい状態になり、
比較的マシなコードの状態になったのではと思います。
結果
川渡りパズルをプログラムにすることができました。
https://github.com/naokiur/design-pattern-sample/tree/master/src/main/java/jp/ne/naokiur/design/pattern/mediator
参考にさせて頂きました
https://github.com/iluwatar/java-design-patterns
http://language-and-engineering.hatenablog.jp/entry/20120330/p1