読者です 読者をやめる 読者になる 読者になる

システム開発で思うところ

JavaEEを主にシステム開発をしながら思うところをツラツラを綴る

EnumにInterfaceを使ってユーティリティを作成する

前回の続き
vermeer.hatenablog.jp

前回のEnumのようにテーブル値と表記がペアとなっているものは
Interfaceを持たせて共通に操作する仕組みを作成しておくと便利です。

Enumクラスにメソッドを追加

package com.mycompany.samples.enumclass;

import lombok.NonNull;

public enum GenderNoLombok {
    MALE(1, "男"),
    FEMALE(2, "女");

    private final int code;
    private final String property;

    private GenderNoLombok(int code, String property) {
	this.code = code;
	this.property = property;
    }

    public int getCode() {
	return code;
    }

    public String getProperty() {
	return property;
    }

    public static GenderNoLombok codeOf(int code) {
	for (GenderNoLombok enumClass : values()) {
	    if (enumClass.getCode() == code) {
		return enumClass;
	    }
	}
	throw new IllegalArgumentException();
    }

    public static GenderNoLombok propertyOf(@NonNull String property) {
	for (GenderNoLombok enumClass : values()) {
	    if (enumClass.getProperty().equals(property)) {
		return enumClass;
	    }
	}
	throw new IllegalArgumentException();
    }
}

シンプルに実装すれば、こんな感じになると思います。
Interfaceもいりません。
これはこれでシンプルで良いと思いますし、嫌いではないです。*1
ただ、すべてのEnumクラスに同じような記述しないといけないので冗長です。

Interfaceとユーティリティを作成

Interface
package com.mycompany.samples.enumclass;

public interface EnumCodePropertyInterface {

    public Object getCode();

    public String getProperty();

}
Enumクラス

共通操作が出来るようにInterfaceをimpliment

package com.mycompany.samples.enumclass;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor @Getter
public enum Gender implements EnumCodePropertyInterface {
    MALE(1, "男"),
    FEMALE(2, "女");

    private final Integer code;
    private final String property;

}

Enumクラス側はlombokのおかげでOverrideの警告も出ません。

ユーティリティ

implimentしたInterfaceに合わせた操作を実装
今回は、code値/property値、それぞれの値から、その値とペアになるEnumクラスを生成するstaticメソッドを作成しました。
また、code値については、テーブル定義に左右されるのでObjectにしています。
もし型安全にするのであれば、想定する型のメソッドを準備してオーバーロードしておけば良いと思います。
propertyは明らかに文字列だけということでString固定にしています。

package com.mycompany.samples.enumclass;

import lombok.NonNull;

public class EnumCodeProperty {

    public static <E extends Enum<E> & EnumCodePropertyInterface> E codeOf(Class<E> enumType, @NonNull Object code) {
	for (E type : enumType.getEnumConstants()) {
	    if (type.getCode().equals(code)) {
		return type;
	    }
	}
	throw new IllegalArgumentException();
    }

    public static <E extends Enum<E> & EnumCodePropertyInterface> E propertyOf(Class<E> enumType, @NonNull String property) {
	for (E type : enumType.getEnumConstants()) {
	    if (type.getProperty().equals(property)) {
		return type;
	    }
	}
	throw new IllegalArgumentException();
    }
}
実行コード
package com.mycompany.samples.enumclass;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class EnumTest {

    public void execUtil() {
	GenderNoLombok genderNoLombok = GenderNoLombok.codeOf(GenderNoLombok.MALE.getCode());
	System.out.println("genderNoLombok.getCode() = " + genderNoLombok.getCode());
	System.out.println("genderNoLombok.getProperty() = " + genderNoLombok.getProperty());

	genderNoLombok = GenderNoLombok.propertyOf(GenderNoLombok.FEMALE.getProperty());
	System.out.println("genderNoLombok.getCode() = " + genderNoLombok.getCode());
	System.out.println("genderNoLombok.getProperty() = " + genderNoLombok.getProperty());

	Gender gender = EnumCodeProperty.codeOf(Gender.class, Gender.MALE.getCode());
	System.out.println("gender.getCode() = " + gender.getCode());
	System.out.println("gender.getProperty() = " + gender.getProperty());

	gender = EnumCodeProperty.propertyOf(Gender.class, Gender.FEMALE.getProperty());
	System.out.println("gender.getCode() = " + gender.getCode());
	System.out.println("gender.getProperty() = " + gender.getProperty());

    }
}
実行結果
genderNoLombok.getCode() = 1
genderNoLombok.getProperty() = 男
genderNoLombok.getCode() = 2
genderNoLombok.getProperty() = 女
gender.getCode() = 1
gender.getProperty() = 男
gender.getCode() = 2
gender.getProperty() = 女

最後に

Enumが通常のクラスであればInterfaceではなく抽象クラスで今回と同様の操作を実装できるかもしれませんがEnumは継承をさせることが出来ません。
そういった技術的な側面もありますが、Enumは定数定義のためのクラスとして、あまり多くの事をしすぎないというのも、1つの整理なのかなぁと思ったりもします。*2

この「Interface+ユーティリティ」のやり方は、Enumだけでなく、リファクタリングをしているときに個人的によく使っています。
ユーティリティで実装の集約を一旦した上で、主語と述語を整理しユーティリティからクラスに格上げをする、というやり方をしています。ロジックだけを見たら似ている気がしたけど概念で考えると違ったり、ユーティリティで汎用的な実装へリファクタリングをしている中で、同分類とすべきドメインが見つかって更に見直しを深めるということは結構ありました。


次は、このEnumクラスを使って、JSFのSelectItemについて汎用的な部品クラスの作成について整理してみたいと思います。*3

追記:2017/1/2

EnumのInterfaceに共通処理を実装してみました。
vermeer.hatenablog.jp

*1:ちなみに以前は、こういう実装をしていました

*2:実はEnumでもInterfaceのdefault実装で対応をしよう色々としたのですが、うまくいかず、トータルの実装量を鑑みて落としどころをユーティリティしたというのが本当のところです

*3:実際の整理の流れは逆です。SelectItemの部品化をしようと思ったのが先で、その過程でEnumの整理に至りました