#初めに
自分がコードを書く際にEnumをよく使うようになってきたので実際に使った実例をまとめていこうと思います。
コードはKotlinで記述します。記述方法が異なりますがJavaでも同じような仕組みで実装できると思います。(他の言語でもできるかも)
#コード値⇔名称の変換処理
「データ上コード値(01とか)で扱うが画面に出す場合は名称で出したい」のような仕様があった場合、コード値から名称へ名称からコード値への変換処理が必要になります。
以下では単純な条件分岐を使用した場合とEnumを使用した実装方法を挙げていきます。
####条件分岐を使用する方法
思いつくコードは以下が挙げられます。
// コードを名称に変換
fun convert(code : String) : String {
if ("01" == code) {
return "名称1"
} else if ("02" == code) {
return "名称2"
}
// 以降、コード値の数だけ条件分岐を追加
// 該当しなかった場合、そのまま返す。
// ここの仕様はそれぞれあります。
return code
}
条件分岐はswitch文でもwhen文でも構いません。上記はコード値の変換処理のため、名称からコード値への変換処理も作成する必要があります。
####Enumを使用する方法
条件分岐を使用する方法でも実装はできますが、コード値の変更や追加があった場合に毎回二つの処理を変更しなければいけません。単純にめんどくさいです。
Enumを使用する方法ではEnumが定数の列挙のみではなく変数やメソッドを定義できるクラスであることを利用してコードの数だけ条件分岐を書かないように実装します。
このように実装することで余分な条件分岐を削除し、変更や追加があった場合でもEnumの定義のみ変更することで済みます。
enum class Test(val code : String, val value : String) {
// コード値の変更があった場合、ここを修正する
KUBUN1("01", "名称1"),
KUBUN2("02", "名称2"),
KUBUN3("03", "名称3");
// Javaでいうところのstaticメソッドの定義
companion object {
fun toValue(code : String) : String {
// Enumの列挙に該当するものがあればそいつを返却
for (item in enumValues<Test>()) {
if (item.code == code) {
return item.value
}
}
// 該当するものがない場合は入力をそのまま返却
return code
}
fun toCode(value : String) : String {
// Enumの列挙に該当するものがあればそいつを返却
for (item in enumValues<Test>()) {
if (item.value == value) {
return item.code
}
}
// 該当するものがない場合は入力をそのまま返却
return value
}
}
}
Test.toValue(任意の値)のような使い方で変換処理を使用することができます。
上記の例であっても変更が入った場合にはクラスの定義は変更しますが、条件分岐を使用する例と異なる部分は処理とデータが分離していることにあります。条件分岐の例ではデータと処理が一体となっているためデータを修正した場合は処理も修正しなければいけませんが、Enumの例ではデータを変更するのみで処理の変更をする必要がありません。
####もっとデータを分離するには
「とにかくクラスにデータを持ちたくない」という場合は外部ファイルに定義を記述してそれを読み込んでEnumを構築するということもできると思います。(Jsonにしてみるとか)
またはデータベースに登録しておくとか。そもそも処理でコード値を扱うという仕様ならばそのデータはデータベースに登録する用の値であることが多いような気がするので、こっちの方が現実的ですかね。
これらはファイルI/Oの回数やらデータベースアクセスなどの回数が増えることで性能への影響が出るため、望ましくはシングルトンなどの対策をする方がいいかもしれません。同時にスレッドセーフも。
####蛇足
「Enumクラスはデータのみにして処理は別に書きたい」という場合は、インターフェースを利用して実装することが可能です。
interface ITest {
fun code() : String
fun value() : String
}
enum class Test(val code : String, val value : String) : ITest {
KUBUN1("01", "名称1"),
KUBUN2("02", "名称2"),
KUBUN3("03", "名称3");
override fun code(): String {
return code
}
override fun value(): String {
return value
}
companion object {
// Javaは配列の型をインターフェースにすればOK
// Kotlinはクラスなのでちょいとひと手間
val VALUE : Array<ITest> = Arrays.copyOf(values(), values().size)
}
}
class Hoge {
companion object {
fun toValue(items : Array<ITest>, code: String): String {
// Enumの列挙に該当するものがあればそいつを返却
for (item in items) {
if (item.code() == code) {
return item.value()
}
}
// 該当するものがない場合は入力をそのまま返却
return code
}
fun toCode(items : Array<ITest>, value: String): String {
// Enumの列挙に該当するものがあればそいつを返却
for (item in items) {
if (item.value() == value) {
return item.code()
}
}
// 該当するものがない場合は入力をそのまま返却
return value
}
}
}
fun main(args: Array<String>) {
// 名称2が出てくる
println(Hoge.toValue(Test.VALUE, "02"))
}
元々あった変換の処理は入力をインターフェースで受け取り、中の処理はインターフェースの実装処理を呼び出します。
インターフェースを使った理由はEnumをいろいろ定義するが用途が同じ場合、インターフェースを使用することで変換処理がメソッド一つで実現できるためです。Enumを一つしか作らない場合は必要ありません。
#任意の項目の組み合わせによる判断処理
処理状況と状態という項目があり、上記の組み合わせによりbooleanを返却するような処理を実現したい場合、各項目をチェックして結果を返却する処理が必要になります。
縦列を処理状況、横列を状態とした時の組み合わせを以下に示します。○はtrue、×はfalseを返却すると考えてください。
01 | 02 | 03 | |
---|---|---|---|
1 | ○ | × | × |
2 | × | × | × |
3 | ○ | × | ○ |
以下では、条件分岐を使用した処理とEnumを使用した実現方法を挙げていきます。
####条件分岐を使用する方法
方法としては以下があります。この処理ではtrueの条件のみ抽出し、それ以外をfalseとして条件に起こしています。
全ての条件分岐を記述しても構いません。
// 入力をチェックし、結果を返却する
fun validate(status : String, processStatus: String) : Boolean {
if (status == "1" && processStatus == "01") {
return true
} else if (status == "3" && processStatus == "01") {
return true
} else if (status == "3" && processStatus == "03") {
return true
} else {
return false
}
}
####Enumを使用する方法
条件分岐を使用する方法のよくない箇所として条件分岐で使用する値がそれぞれ何を意味しているかが不明な点です。ほとんどの場合、コード値で表現されるものには何かしらの意味が設定されているはずです。
処理状況「01」は処理待ち、「02」は処理済みとか。状態「1」は正常、「2」はエラーとか。例えばですが、こういった意味があるはずですが、この条件分岐ではそれが読み取れません。
こういった場合、定数などを定義して意味をつける方法が取られます。定数の数が多くなったりすると煩雑になるため、Enumを使用してまとめたりします。
では、Enumを使用して定数を作成し条件分岐に意味をつけましょう。
?
「待って欲しい、Enumを使用するメリットは条件分岐に意味をつけることであったか」
はい、その通りですね。意味を求めるのであれば定数でもEnumでもいいと思います。
(設計として定数で意味のあるまとまりを作りたいならEnumがいいと思います)
Enumがクラスであることを利用するのであればもう少しクラスっぽい使い方を目指してみます。
以下の処理では状態(横列)でグルーピングし、処理状況と状態の組み合わせでbooleanを返却するEnumを作成します。
処理の考え方は条件分岐を使用する方法と同じになります。
enum class StatusEnum {
// 状態でEnumを定義
// 状態が1で処理状況が01のときtrue
STATUS_1 {
override fun validate(status: String, processStatus: String): Boolean {
return status == "1" && processStatus == "01"
}
},
// 状態:2は常にfalse
STATUS_2 {
override fun validate(status: String, processStatus: String): Boolean {
return false
}
},
// 状態が3で処理状況が02以外のときtrue
STATUS_3 {
override fun validate(status: String, processStatus: String): Boolean {
return status == "3" && processStatus != "02"
}
};
abstract fun validate(status : String, processStatus : String) : Boolean
// ここで入力で渡された情報をチェックする
companion object {
fun isStatusValidate(status : String, processStatus : String) : Boolean {
for (item in values()) {
val result = item.validate(status, processStatus)
if (result) {
return result
}
}
return false
}
}
}
状態でまとめ、文字リテラル自体に意味を持たせつつEnumは自分の担当する状態のチェックを行い、外部から使用される想定のisStatusValidate()ではEnumの処理を呼び出し、結果を返却するようになっています。
(意味を持たせるなら状態の意味自体を変数名にするべきですが、ここでは例のためそのままを名称としています)
そのため、状態が増えた場合にはEnumの定義を増やし、処理状況が変更になった場合は影響のあるEnumの返却処理のみ変更するだけで良くなります。長い条件文をごちゃごちゃさせなくて良くなります。
####蛇足
上記の例ではEnumの処理で入力の値をチェックする処理を記述していますが、せっかく状態でグループを作っているのでグループにした方は変数にする方がより意味合いが伝わりやすいかもと思いました。
以下ではEnumの生成時に入力として状態を渡す例となります。
enum class StatusEnum(val status : String) {
// 状態でEnumを定義
// 状態が1で処理状況が01のときtrue
STATUS_1("1") {
override fun validate(status: String, processStatus: String): Boolean {
return this.status == status && processStatus == "01"
}
},
// 状態:2は常にfalse
STATUS_2("2") {
override fun validate(status: String, processStatus: String): Boolean {
return false
}
},
// 状態が3で処理状況が02以外のときtrue
STATUS_3("3") {
override fun validate(status: String, processStatus: String): Boolean {
return this.status == status && processStatus != "02"
}
};
abstract fun validate(status : String, processStatus : String) : Boolean
companion object {
fun isStatusValidate(status : String, processStatus : String) : Boolean {
for (item in values()) {
val result = item.validate(status, processStatus)
if (result) {
return result
}
}
return false
}
}
}
うーん。まさに蛇足という感じですね。
Enumの変数名が状態の意味を名称に使っている場合、その名称の具体的な値として"1"やら"2"やらが定義され、状態のチェックは自分自身と同じかどうか、というふうに見えるのでより読みやすくなる・・・ような気がしますが、ここまでくるとどっちでもいいような気がします。
#終わりに
Enumがクラスであることを利用すると単純な定数の列挙のみではなく、処理やプロパティの定義もできることがわかりました。
このことにより不要な条件文などを削除できたり、設計をシンプルにできる(記述するコードの行数が減るなど)ことにつながるので、いろいろ検討する価値があると思いました。