개발하면서 sealed class를 잘 모르고 썼던 것 같아서 이번 토픽으로 정하게 되었습니다. Navigation, State등의 상태 나열 등에 썼었던 것 같은데 왜 sealed로 선언하는지 그 이유를 보겠습니다.
Sealed class
는 자기 지신이 추상 클래스이고, 자신을 상속받는 서브 클래스를 가질 수 있습니다.또한 부모 클래스를 상속받는 자식 클래스의 종류을 제한합니다.
open class State(
val data: String? = null
)
open class
로 선언된 State
를 상속받는 클래스는 다른 곳에서 참조하여 상속받아 사용할 수 있습니다.
sealed class State()
하지만 sealed class
는 해당 패키지 내에서만 자식 클래스를 상속할 수 있는 추상 클래스입니다.
추상 클래스이기 때문에 sealed class의 인스턴스를 생성할 수는 없습니다.
sealed class State
object Success : State()
object Failure : State()
sealed class State{
object Success : State()
object Failure : State()
}
sealed class
와 그 하위 클래스는 한 패키지 내에 존재하기 때문에, 컴파일 시간에 컴파일러에게 이들이 알려집니다. 그렇기 때문에 반대로 다른 파일에서는 하위 클래스들을 선언하면 오류가 발생합니다.
동일 패키지 내라는 것은
package com.evergreen.todaycommit.domain.usecase
라는 패키지가 있다면 이usecase
내에서만 선언되어야 한다는 뜻입니다.
sealed class State()
class Success : State()
class Failure : State()
sealed class State(){
private constructor(data: String): this()
protected constructor(data: String, data2: String): this()
}
private
, protected
이 두 가시성 만을 가질 수 있습니다.
여태까지 특징을 설명했지만 어떤 상황에 사용하는 지 아직까지 감이 오지 않을 것 입니다.
sealed class
의 장점은 자식 클래스를 패키지 내에 제한하는 sealed class
의 특징에서 나옵니다.
when을 사용할 때, 하위 클래스들의 케이스를 else 없이 커버할 수 있습니다.
이런 예시를 한번 생각해 봅시다.
어떠한 상태를 표현하려고 합니다
이러한 경우에 따라 메시지를 출력하는 예제를 만들어 보겠습니다.
abstract class State
class Loading : State()
class Success : State()
class Error : State()
fun test(state: State){
return when(state){
is Success -> "성공"
is Loading -> "로딩중"
is Error -> "에러"
}
}
위 코드는 컴파일시 아무런 오류가 없을까요? 개발자의 입장에서는 State
의 모든 케이스를 커버해 줬으니 오류가 나지 않을 것이라고 예상합니다. 하지만 결과는 이렇습니다.
내용을 보면, else 브랜치를 필요로 한다는 문구를 볼 수 있습니다. 왜냐하면, 컴파일러가 State를 상속받은 하위 클래스들의 종류를 다 알지 못하기 때문입니다.
그렇다면 이 코드에서 else -> "그 외 상태" 문구를 추가해 주면 끝이지 않겠느냐 하는 생각이 드실 수도 있습니다.
컴파일러가 딱히 오류를 잡아주지 않아 정상적인 코드라고 생각할 수 있지만, Error라는 상태를 else에서 상태가 없음으로 체크하고 있습니다.
프로그래밍에 있어서 개발자의 실수를 줄이기 위해 많은 노력을 해야만 하는데, 이러면 실수는 커녕 실수했는지도 모르는 상황이 발생해 버립니다.
sealed class State
class Loading : State()
class Success : State()
class Error : State()
fun test(state: State): String{
return when(state){
is Success -> "성공"
is Loading -> "로딩중"
is Error -> "에러 발생"
}
}
sealed class
로 선언된 State
를 상속 받았을 때에는 else
브랜치가 없어도 오류가 나지 않고 있습니다. 왜냐하면 컴파일러가 State
의 자식 클래스를 알고있기 때문입니다.
자 이제 sealed class
의 장점에 대해 알 수 있게 되었습니다. 추가적으로 왜 class에 초록색 출이 쳐져 있는지 알아보겠습니다.
sealed class
의 sub class가 상태를 가지고 있지 않거나, equals
를 상속받고 있지 않다고 말합니다. 따라서 이를 object
로 변환하는 것을 권하고 있습니다.
왜냐하면 클래스는 기본적으로 hashcode()
,eqauls()
를 가지고 있고, 딱히 상태를 가지고 있지 않은데 class
로 선언되면 생성 될 수록 메모리가 낭비되기 때문입니다.
코틀린에서 object
로 선언하게 되면 singleton으로 생성되므로, 이럴 때에는 object로 선언해 줘야 합니다.
sealed class State{
object Loading : State()
data class Success(val data: String) : State()
data class Error(val throwable: Throwable, val message: String) : State()
}
또한 여기서enum class와의 차이점이 나오는데, enum class에서는 열거형을 표현하기 위해 클래스의 타입이 정해져 있고 object로 설정되지만 sealed class 는 하위 클래스들이 여러 객체를 생성할 수 있습니다.
참고 자료
https://kotlinlang.org/docs/sealed-classes.html#location-of-direct-subclasses
https://kotlinworld.com/165?category=924651
크리스마스 씰은 없나요?