Help us understand the problem. What is going on with this article?

列挙型でクラスの振り分けをしてみる

More than 3 years have passed since last update.

環境

このページにあるJavaは
1.8
で組んでいます。

変更前

下記のようなインターフェースがあったとして

Logic.java
public interface Logic {
    public void printSkyColor();
}

こんな感じで実装されていました。
種別的なもので実装クラスを呼び分ける感じ。

    public void execute(String type) {
        Logic logic = null;
        if("S".equals(type)){
            logic = new SunnyLogic();
        }else if("C".equals(type)){
            logic = new CloudyLogic();
        }else if("R".equals(type)){
            logic = new RainyLogic();
        }else{
            throw new UnsupportedOperationException("unknown type : "+ type);
        }

        //SunnyLogic -> "It is Blue." が標準出力される
        //CloudyLogic-> "It is White." が標準出力される
        //RainyLogic-> "It is Gray." が標準出力される
        logic.printSkyColor();
    }

これって種別に対応したロジックを「列挙」してるってとらえれるよね?
列挙型をうまく使えば少し綺麗になるのかな?

と考え、実際にやってみました。

Enum化

まずは列挙クラスをこんな感じに作成。

LogicConstant.java
public enum LogicConstant {
    SUN("S", new SunnyLogic()),
    CLOUD("C", new CloudyLogic()),
    RAIN("R", new RainyLogic()),
    ;

    private final String type;
    private final Logic logic;

    private LogicConstant(String type, Logic logic) {
        this.type = type;
        this.logic = logic;
    }

    public static Logic ofLogic(String type) {
        for(LogicConstant c : values()){
            if(c.type.equals(type)){
                return c.logic;
            }
        }
        return null;
    }
}

上記enumを使用する側はこんな感じになります。

    public void execute(String type) {
        Logic logic = LogicConstant.ofLogic(type);
        if(logic == null){
            throw new UnsupportedOperationException("unknown type : "+ type);
        }

        //System.out.print(logic.hashCode()+":");
        logic.printSkyColor();
    }

これでもまぁ動くは動くんですが、
変更前と決定的に違うのがロジッククラスのインスタンス。
変更前は毎回インスタンスを生成しますが、変更後は同一インスタンス。
という点です。

ソース見ればわかると思いますが、

//System.out.print(logic.hashCode()+":");

使用側にあるこのコメントアウトを外すとハッシュ値の確認ができます。



同一インスタンスの方が都合がイイ!というケースがあるかもしれませんが
今回は「変更前を踏襲」ということで別インスタンス化になるようにしてみます。

Enum化 その2

別インスタンス化ということは生成するクラスだけを定義して
呼び出し時にnewするということなのでこんな感じに。

LogicConstant.java
public enum LogicConstant {
    SUN("S", SunnyLogic.class),
    CLOUD("C", CloudyLogic.class),
    RAIN("R", RainyLogic.class),
    ;

    private final String type;
    private final Class<? extends Logic> logic;

    private LogicConstant(String type, Class<? extends Logic> logic) {
        this.type = type;
        this.logic = logic;
    }

    public static Logic ofLogic(String type) {
        for(LogicFactory c : values()){
            if(c.type.equals(type)){
                try {
                    return c.logic.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }
        return null;
    }
}

ここからもう少し改造。

return c.logic.newInstance();

まず
ClassのnewInstance()はjava9で非推奨となっているので

ClassのgetDeclaredConstructor().newInstance()

java9のjavadocの指定にある通りこっちに置き換えます。

更にどうせならコンストラクタ時にパラメータも渡せるように
ということでパラメータを引き回すように書き換える。

呼び出しごとに別インスタンスを生成ということでクラス名もFactoryにする。

最後に
null返却ではなく、例外を投げるように修正。(ご指摘ありがとうございます!)

これを反映して・・・
できあがったのがコレ。

LogicFactory.java
public enum LogicFactory {
    SUN("S", SunnyLogic.class),
    CLOUD("C", CloudyLogic.class),
    RAIN("R", RainyLogic.class),
    ;

    private final String type;
    private final Class<? extends Logic> logic;

    private LogicFactory(String type, Class<? extends Logic> logic) {
        this.type = type;
        this.logic = logic;
    }

    public static Logic ofLogic(String type, Object... initargs) {
        for(LogicFactory c : values()){
            if(c.type.equals(type)){
                try {
                    return c.logic.getDeclaredConstructor().newInstance(initargs);
                } catch (
                    InstantiationException | IllegalAccessException | IllegalArgumentException |
                    InvocationTargetException | NoSuchMethodException | SecurityException e
                ) {
                    e.printStackTrace();
                    throw new IllegalArgumentException("type : "+type, e);
                }
            }
        }
        throw new UnsupportedOperationException("unknown type : "+ type);
    }
}

これら修正を受けて
呼び出し側のnull判定も不要となったので削除しておく。



これで個人的に少しは綺麗になったのかなと思ってます。

まぁこういったリファクタリング作業はよく趣味の範疇と言われますが
運用保守を考えて少しでも整理すべきだし、意識してコーディングすべきと考えてます。

enumのお勉強というか復習?かねて記載します。

n_a
java屋です。 でもここでは備忘録という感じで java以外のことをメインに書いていきたいです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away