技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

javaでのChainOfResponsibilityパターン

Chain of Responsibilityパターンとは、その名の通り責任が連鎖する構造を表すためのパターンです。
あるオブジェクトで解決できない問題を別のオブジェクトにたらい回すようにするのがツボで、そうすることで解決できなかった場合の処理を簡略化できます。
 
ソースコードを見た方が理解が早いと思うので、いつも通りサンプルコードを示したいと思います。
某有名RPGを模した例で、3匹の味方モンスターが色々な敵モンスターと戦い、その結果を表示するというサンプルコードです。
(なお、今回のサンプルコードでは、このデザインパターンとは直接関係のないEnumクラスも出てきますが、これは同じ定数が何度も出てくるサンプルであるために簡略化のために使用しているので、「責任をたらい回す」という本質とは関係ありません)
 
【サンプルコード】
・MonsterAttributeEnum.java
// モンスターの属性を示すEnumクラス
//
// 今回のデザインパターンと直接関係ありませんが、
// 共通の定数を使用するため定義します。
public enum MonsterAttributeEnum {
    normal(0),
    fire(1),
    water(2),
    grass(3),
    dragon(4);

    private int id;
    public final static int ATTRIBUTE_TOTAL_NUMBER = 5; // 属性の総数

    private MonsterAttributeEnum (int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public static MonsterAttributeEnum valueOf(int id) {
        MonsterAttributeEnum[ ] array = values();
        for(MonsterAttributeEnum num : array) {
            if (id == num.getId()){
                return num;
            }
        }
        return null;
    }

    // 倒せるかどうかの判定
    public static boolean defeatJudge
        (MonsterAttributeEnum monsterAttributeEnumAlly,
         MonsterAttributeEnum monsterAttributeEnumEnemy) {

        boolean[ ][ ] result = new boolean
            [ATTRIBUTE_TOTAL_NUMBER][ATTRIBUTE_TOTAL_NUMBER];

        result[normal.getId()][normal.getId()] = true;
        result[normal.getId()][fire.getId()] = false;
        result[normal.getId()][water.getId()] = false;
        result[normal.getId()][grass.getId()] = false;
        result[normal.getId()][dragon.getId()] = false;
        result[fire.getId()][normal.getId()] = true;
        result[fire.getId()][fire.getId()] = false;
        result[fire.getId()][water.getId()] = false;
        result[fire.getId()][grass.getId()] = true;
        result[fire.getId()][dragon.getId()] = false;
        result[water.getId()][normal.getId()] = true;
        result[water.getId()][fire.getId()] = true;
        result[water.getId()][water.getId()] = false;
        result[water.getId()][grass.getId()] = false;
        result[water.getId()][dragon.getId()] = false;
        result[grass.getId()][normal.getId()] = true;
        result[grass.getId()][fire.getId()] = false;
        result[grass.getId()][water.getId()] = true;
        result[grass.getId()][grass.getId()] = false;
        result[grass.getId()][dragon.getId()] = false;
        result[dragon.getId()][normal.getId()] = true;
        result[dragon.getId()][fire.getId()] = true;
        result[dragon.getId()][water.getId()] = true;
        result[dragon.getId()][grass.getId()] = true;
        result[dragon.getId()][dragon.getId()] = true;

        return result
            [monsterAttributeEnumAlly.getId()]
            [monsterAttributeEnumEnemy.getId()];

    }

}
 
・EnemyMonster.java
// 敵モンスターを示すクラス
public class EnemyMonster {

    private int attributeId;

    public EnemyMonster(int attributeId) {
        this.attributeId = attributeId;
    }

    public int getAttributeId() {
        return attributeId;
    }

}

・AllyMonster.java
// 味方モンスターを示すクラス
//
// 他の文献では抽象クラスとして定義されますが、
// 当クラスを具象クラスとして定義してもたらい回しは実現できるため、
// 例を単純化するために具象クラスとして定義します。
public class AllyMonster {

    private int attributeId;

    // たらい回し先
    private AllyMonster nextAlly = null;

    public AllyMonster(int attributeId) {
        this.attributeId = attributeId;
    }

    // たらい回し先のセット
    public AllyMonster setNext(AllyMonster nextAlly) {
        this.nextAlly = nextAlly;
        return nextAlly;
    }

    // 敵モンスターと戦うメソッド
    public void battle(EnemyMonster enemyMonster) {

        // Enum上の名前を取得
        MonsterAttributeEnum allyName =
            MonsterAttributeEnum.valueOf(this.attributeId);
        MonsterAttributeEnum enemyName =
            MonsterAttributeEnum.valueOf(enemyMonster.getAttributeId());

        // 倒せるなら倒す
        // 倒せずにたらい回し先がいるならたらい回す
        // たらい回し先がいないなら倒せない
        if (MonsterAttributeEnum.defeatJudge(allyName,enemyName)) {
            System.out.println
                ("敵の" + enemyName + "モンスターは、" +
                 "味方の" + allyName + "モンスターが倒した。");
        } else if (nextAlly != null) {
            nextAlly.battle(enemyMonster);
        } else {
            System.out.println
            ("敵の" + enemyName + "モンスターは、" +
             "誰も倒せなかった…。");
        }
    }

}
 
・MonsterBattleMain.java
// 味方モンスターが敵モンスターと戦うmainクラス
public class MonsterBattleMain {

    public static void main(String[ ] args) {

        // 味方モンスターの定義
        AllyMonster fire =
            new AllyMonster(MonsterAttributeEnum.fire.getId());
        AllyMonster water =
                new AllyMonster(MonsterAttributeEnum.water.getId());
        AllyMonster grass =
                new AllyMonster(MonsterAttributeEnum.grass.getId());

        // 味方モンスターのたらい回し先の設定
        fire.setNext(water).setNext(grass);

        // 敵モンスターとの戦闘
        for (int i = 0; i < 5; i++) {
            System.out.println
                (MonsterAttributeEnum.valueOf(i) + "モンスターが現れた!");
            fire.battle(new EnemyMonster(i));
            System.out.println();
        }
    }

}
 
【実行結果】
normalモンスターが現れた!
敵のnormalモンスターは、味方のfireモンスターが倒した。

fireモンスターが現れた!
敵のfireモンスターは、味方のwaterモンスターが倒した。

waterモンスターが現れた!
敵のwaterモンスターは、味方のgrassモンスターが倒した。

grassモンスターが現れた!
敵のgrassモンスターは、味方のfireモンスターが倒した。

dragonモンスターが現れた!
敵のdragonモンスターは、誰も倒せなかった…。