코틀린

sealed class 를 이용한 상태관리

팀(Tim) 2021. 4. 28. 22:18

 

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의 장점을 모두 취한셈입니다.