🤐
Kotlin の sealed class で enum class の values(), valueOf() が欲しいを解決する
この記事は、「イエソド アウトプット筋 トレーニング Advent Calendar 2021」の10日目の記事です。
sealed class
を使って列挙を表現したときに、 enum class
にある values()
, valueOf()
等のメソッドが欲しいときに使える、インターフェースと拡張関数の組み合わせを紹介します。
sealed class を列挙に使うって、例えばどういう時に使うの?
たとえば、ValueObjectを使っているときに、特定の値の塊に意味を持たせたい場合に使えます。 enum class
はValueObjectのクラスを継承できませんが、 sealed class
は継承できます。
open class AttributeId<out T>(val uuid: UUID)
data class ReferenceEntityId(val uuid: UUID)
sealed class ReferenceAttributeId(uuid: UUID) :
AttributeId<ReferenceEntityId>(uuid) {
object COMPANY : ReferenceAttributeId(UUID(0, 1))
object ORGANIZATION : ReferenceAttributeId(UUID(0, 2))
object OFFICE : ReferenceAttributeId(UUID(0, 3))
object PROJECT : ReferenceAttributeId(UUID(0, 4))
}
sealed class
なので、例えば下のようなwhen式の中に else ->
を書かなくてよく、もし ReferenceAttributeId
に新しく項目を追加したときには、when式に項目が足りないとコンパイラが怒ってくれます。
fun toJapaneseLabel(referenceAttributeId: ReferenceAttributeId): String =
when (referenceAttributeId) {
ReferenceAttributeId.COMPANY -> "会社"
ReferenceAttributeId.ORGANIZATION -> "組織"
ReferenceAttributeId.OFFICE -> "オフィス"
ReferenceAttributeId.PROJECT -> "プロジェクト"
}
values(), valueOf() のために、とりあえずこんな感じの物を用意する
インターフェース名は適当です。計算量も適当なのでヘビーな環境だとつらいかもしれません(改良の余地はあります)。
interface SealedClassEnumExtension<T>
interface SealedClassEnumWithName {
val name get(): String = this::class.simpleName ?: "N/A"
}
inline fun <reified T> SealedClassEnumExtension<T>.values(): List<T> {
return T::class.sealedSubclasses.mapNotNull { it.objectInstance }
}
inline fun <reified T : SealedClassEnumWithName> SealedClassEnumExtension<T>.valueOf(name: String): T {
return values().find { it.name == name } ?: throw IllegalArgumentException(name)
}
上のインターフェースと拡張関数をどう適用するの?
sealed class
に SealedClassEnumWithName
をインターフェースとして指定し、 その companion object
で SealedClassEnumExtension<T>
を継承します。
.name
や valueOf()
が必要無ければ、 SealedClassEnumWithName
をインターフェースに指定する必要はありません。
open class AttributeId<out T>(val uuid: UUID)
data class ReferenceEntityId(val uuid: UUID)
sealed class ReferenceAttributeId(uuid: UUID) :
AttributeId<ReferenceEntityId>(uuid), SealedClassEnumWithName {
object COMPANY : ReferenceAttributeId(UUID(0, 1))
object ORGANIZATION : ReferenceAttributeId(UUID(0, 2))
object OFFICE : ReferenceAttributeId(UUID(0, 3))
object PROJECT : ReferenceAttributeId(UUID(0, 4))
companion object : SealedClassEnumExtension<ReferenceAttributeId>
}
どんなかんじでつかえるの?
こんなかんじです。
fun main() {
println(ReferenceAttributeId.values().map { it.name })
// output: [COMPANY, ORGANIZATION, OFFICE, PROJECT]
println(ReferenceAttributeId.valueOf("COMPANY").name)
// output: COMPANY
fun toJapaneseLabel(referenceAttributeId: ReferenceAttributeId): String =
when (referenceAttributeId) {
ReferenceAttributeId.COMPANY -> "会社"
ReferenceAttributeId.ORGANIZATION -> "組織"
ReferenceAttributeId.OFFICE -> "オフィス"
ReferenceAttributeId.PROJECT -> "プロジェクト"
}
println(toJapaneseLabel(ReferenceAttributeId.COMPANY))
// output: 会社
}
まとめ
いかがでしたでしょうか?
かゆい
うま
Discussion