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

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

タイプコードをサブクラスで置き換える

前回はタイプコードをクラスで置き換えたが、今回はサブクラスで置き換える方法。タイプコード別に動作が異なるようならサブクラスで置き換える。

// アイテムクラス
public class Item {
    public static final int ITEMTYPE_WEAPON = 0;
    public static final int ITEMTYPE_ARMOR = 1;
    public static final int ITEMTYPE_SHIELD = 2;

    private final int itemtype;

    public Item(int itemtype) {
        this.itemtype = itemtype;
    }

    public void use() {
        switch(itemtype) {
        case ITEMTYPE_WEAPON:
            System.out.println("use weapon");
            break;
        case ITEMTYPE_ARMOR:
            System.out.println("use armor");
            break;
        case ITEMTYPE_SHIELD:
            System.out.println("use shield");
            break;
        default:
            ;
        }
    }
}
// テストメイン
public class Main {
    public static void main(String[] args) {
        Item weapon = new Item(Item.ITEMTYPE_WEAPON);
        Item armor  = new Item(Item.ITEMTYPE_ARMOR);
        Item shield = new Item(Item.ITEMTYPE_SHIELD);

        weapon.use();
        armor.use();
        shield.use();
    }
}

useメソッド内でswitch文でアイテムの種類別に動作を変えている。このswitch文をなくしてサブクラスへと移動する。

手順

・タイプコードを表すフィールドを隠して、getterメソッドで見せるようにする
・タイプコードを元にインスタンスを作成しているなら、Factory Methodにする
・タイプコードそれぞれの値ごとにサブクラスを作成
タイプコードのgetterメソッドをオーバーライド。switch文での動作を移動

コンパイルしてテスト。

・タイプコードのフィールドを削除
・元クラスのメソッドのうち、サブクラスで実装するメソッドはabstractにする

コンパイルして確認。


上の例をリファクタリングした結果。途中経過は省略。

// アイテム抽象クラス
public abstract class Item {
    public static final int ITEMTYPE_WEAPON = 0;
    public static final int ITEMTYPE_ARMOR = 1;
    public static final int ITEMTYPE_SHIELD = 2;

    public static Item createItem(int itemtype) {
        switch(itemtype) {
        case ITEMTYPE_WEAPON:
            return new ItemWeapon();
        case ITEMTYPE_ARMOR:
            return new ItemArmor();
        case ITEMTYPE_SHIELD:
            return new ItemShield();
        default:
            throw new IllegalArgumentException("itemtype = " + itemtype);
        }
    }
        
    public abstract void use();
}

createItemメソッドでタイプコードごとのサブクラスのインスタンスを作成している。あるいは各サブクラスごとのcreateメソッドを用意する方法もある。
タイプコードのgetterはこの例では使用しないのでばっさりカット。
実際に作成されるインスタンスは各サブクラスなので、abstractを付ける。useメソッドもabstractを付ける。

// タイプコードごとのサブクラス
public class ItemWeapon extends Item {
    @Override public void use() {
        System.out.println("use weapon");
    }
}

public class ItemArmor extends Item {
    @Override public void use() {
        System.out.println("use armor");
    }
}

public class ItemShield extends Item {
    @Override public void use() {
        System.out.println("use shield");
    }
}

前のクラスでswitch文で書かれたuseメソッドをそれぞれのuseメソッドへと移動。

// テストメイン
public class Main {
    public static void main(String[] args) {
        Item weapon = Item.createItem(Item.ITEMTYPE_WEAPON);
        Item armor  = Item.createItem(Item.ITEMTYPE_ARMOR);
        Item shield = Item.createItem(Item.ITEMTYPE_SHIELD);

        weapon.use();
        armor.use();
        shield.use();
    }
}

使用する側はcreateItemメソッドでインスタンスを作成する。


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