enum 대신 sealed class를 이용해서 상태관리를 하는 이유는
enum의 장점과 class의 장점을 모두 활용하기 위해서입니다.
1. enum을 쓰는 이유
int SOME_STATE_A = 0
int SOME_STATE_B = 1
만약 enum을 안쓰고 이렇게 상수로 상태를 정의한다고 한다면
컴파일러는 저 두개의 상수가 똑같이 SOME_STATE를 나타낸다는 것을 모를 것입니다.
따라서 when을 이용해 상태관리를 할 경우에 아래처럼 쓰게됩니다.
fun foo() : Any {
return when(state)
SOME_STATE_A -> //do something..
SOME_STATE_B -> //do something..
else -> throw exception
}
반면에 enum을 이용하면 컴파일러가 모든 상태를 알고 있기 때문에 else 구문을 없앨 수 있습니다.
enum class SOME_STATE{
A, B
}
fun foo() : Any {
return when(state)
A -> //do something..
B -> //do something..
}
만약 이 상태에서 SOME_STATE_C가 추가된다면 어떨까요? 상수로 상태를 관리하는 경우에는 else구문으로 들어가서 런타임 에러가 뜰것입니다.
enum으로 상태를 관리하면 when이 C의 경우를 커버하지 않기 때문에 컴파일 에러가 뜰 것입니다.
2. seal class를 쓰는이유
각 enum들은 enum class의 인스턴스로 취급됩니다.
enum class Item(val data : String) {
ITEM_A("a") {
override fun foo() {}
},
ITEM_B("b") {
override fun foo() {}
},
ITEM_C("c") {
override fun foo() {}
};
abstract fun foo()
}
seal class로 상태를 구현하면 각 상태들은 seal class를 상속받는 class나 object입니다.
seal class는 외부 파일에서 상속을 제한하는 키워드입니다.
즉, 컴파일시점에 seal class를 상속받는 클래스를 컴파일러가 모두 알 수 있습니다.
(컴파일은 소스파일 단위로 이루어짐)
이런 특징 덕분에 아래처럼 사용할 수 있습니다.
sealed class Item(){
data class A(val name : String) : Item()
data class B(val value : Int) : Item()
data class C(val canUse : Boolean) : Item()
}
fun processItem(item : Item) : String {
return when(item){
is Item.A -> item.name
is Item.B -> item.value.toString()
is Item.C -> item.canUse.toString()
}
}
when 에서 is 로 sealed class의 타입을 확인하고 있습니다.
컴파일러는 sealed class의 서브클래스가 어떤어떤게 있는지 알고 있기 때문에 모든 경우의 수를 커버하지 않으면 컴파일 에러를 발생시킵니다. 또한 else문을 쓸 필요도 없습니다.
이처럼 enum클래스의 장점을 취하면서도 sealed class를 상속받는 각 클래스는 각자 고유의 필드와 메서드를 가질 수 있습니다.
즉 enum의 장점과 class의 장점을 모두 취한셈입니다.