LoginSignup
2
6

More than 5 years have passed since last update.

Mediatorパターンを使って、川渡りパズルを表現した。

Posted at

最近のフルタイムは、運用改善おじさんです。
デザインパターンを勉強し直しています。

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は、
以下を継承しています。

CharacterBase.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)メソッドを実装しています。

Farmer.java
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がいない場合

各キャラクターを全て知っているクラスが居ないため、
実行クラス内で、逐一状況を確認し、
キャラクターのステータスを更新しなければならなくなってしまいました。

CrossRiverExecutor.java

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を用意します。

CrossRiverMediator.java

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経由に実行するように変更します。

CharacterBase.java

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);
    }
}

Farmer.java

public class Farmer extends CharacterBase {
    public void cross(Character withCharacter) {
        this.crossRiver.cross(this, withCharacter);
    }
}

そうすると、実行クラスは、
以下のようになります。

CrossRiverExecutor.java

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

2
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6