ゲームが作れるようになるまでがんばる日記

ゲーム制作のことを中心にゲームに関することを書いています

タイプコードをState/Strategyパターンで置き換える

タイプコードをサブクラスで置き換える方法(id:toburau:20070911)の場合、タイプコードが実行中に切り替わるような場合に対応できない。また、Javaでは多重継承できないので、すでにサブクラスになっていたときはタイプコードをサブクラスで置き換えられない。そんなときはデザインパターンのStateパターンやStrategyパターンを使う。

// 敵クラス
public class Enemy {
    public static final int STATE_ATTACK = 0;
    public static final int STATE_DEFENCE = 1;
    private int state;
    public Enemy() {
        state = STATE_ATTACK;
    }
    public void action() {
        switch (state) {
        case STATE_ATTACK:
            System.out.println("Attack");
            break;
        case STATE_DEFENCE:
            System.out.println("Defence");
            break;
        }
    }
    public void change() {
        switch (state) {
        case STATE_ATTACK:
            state = STATE_DEFENCE;
            break;
        case STATE_DEFENCE:
            state = STATE_ATTACK;
            break;
        }
    }
}
// テストメイン
public class Main {
    public static void main(String[] args) {
        Enemy enemy = new Enemy();
        enemy.action();
        enemy.change();
        enemy.action();
    }
}

敵は攻撃と防御の状態を持ち、actionメソッドで行動し、changeメソッドで攻撃だったら防御に、防御だったら攻撃に状態を切り替える。
これをリファクタリングする。

手順

タイプコードを自己カプセル化する
タイプコードを表すクラス(状態オブジェクト)を作る
タイプコードの値ごと状態オブジェクトのサブクラスを作る
タイプコードを得る抽象メソッドを状態オブジェクトに用意
サブクラスでタイプコードを得る抽象メソッドをオーバーライド

コンパイルして確認。

タイプコードを使っているクラスに状態オブジェクトのフィールドを追加
タイプコードを使っているコードをタイプコードを得るメソッドに置き換え
タイプコードを変更しているコードを状態オブジェクトを変更するコードへ置き換え

コンパイルしてテスト。
ここまででリファクタリングした結果は次のとおり。

// 状態を表すクラス
public abstract class State {
    public abstract int getTypeCode();
}
// 攻撃状態を表すクラス
public class StateAttack extends State {
    @Override public int getTypeCode() {
        return Enemy.STATE_ATTACK;
    }
}
// 防御状態を表すクラス
public class StateDefence extends State {
    @Override public int getTypeCode() {
        return Enemy.STATE_DEFENCE;
    }
}
// 敵クラス
public class Enemy {
    public static final int STATE_ATTACK = 0;
    public static final int STATE_DEFENCE = 1;
    private State state;
    public Enemy() {
        setState(STATE_ATTACK);
    }
    public int getState() {
        return state.getTypeCode();
    }
    public void setState(int next) {
        switch(next) {
        case STATE_ATTACK:
            state = new StateAttack();
            break;
        case STATE_DEFENCE:
            state = new StateDefence();
            break;
        }
    }
    public void action() {
        switch (getState()) {
        case STATE_ATTACK:
            System.out.println("Attack");
            break;
        case STATE_DEFENCE:
            System.out.println("Defence");
            break;
        }
    }
    public void change() {
        switch (getState()) {
        case STATE_ATTACK:
            setState(STATE_DEFENCE);
            break;
        case STATE_DEFENCE:
            setState(STATE_ATTACK);
            break;
        }
    }
}

ここまでで、タイプコードをstateという状態オブジェクトで表すことができた。さらにポリモーフィズムを利用してswitch文をなくす。

// 状態を表すクラス
public abstract class State {
    public abstract void action();
    public abstract State change();
}
// 攻撃状態を表すクラス
public class Attack extends State {
    @Override public void action() {
        System.out.println("Attack");
    }
    @Override public State change() {
        return new Defence();
    }
}
// 防御状態を表すクラス
public class Defence extends State {
    @Override public void action() {
        System.out.println("Defence");
    }
    @Override public State change() {
        return new Attack();
    }
}
// 敵クラス
public class Enemy {
    private State state;
    public Enemy() {
        state = new Attack();
    }
    public void action() {
        state.action();
    }
    public void change() {
        state = state.change();
    }
}

状態を表すクラスにactionメソッドとchangeメソッドを実装することによりswitch文をなくすことができた。changeメソッドでは次の状態オブジェクトを返すように実装したがこれでよかったのかちょっと自信が無い。
デザインパターンのStateやStrategyについては過去の日記で書いた。

結果を見るとやっぱり似たようなコードになっている。
なお、Stateパターンの日記ではコードの間違いがあったので修正した。やっぱりちゃんと実行して確認しないとだめだなぁ。


参考文献:『Java言語で学ぶリファクタリング入門』