[Kotlin] sealed class

Choi Sang Rok·2022년 6월 24일
3

Kotlin

목록 보기
6/6

개발하면서 sealed class를 잘 모르고 썼던 것 같아서 이번 토픽으로 정하게 되었습니다. Navigation, State등의 상태 나열 등에 썼었던 것 같은데 왜 sealed로 선언하는지 그 이유를 보겠습니다.



🧷sealed class


Sealed class는 자기 지신이 추상 클래스이고, 자신을 상속받는 서브 클래스를 가질 수 있습니다.또한 부모 클래스를 상속받는 자식 클래스의 종류을 제한합니다.

open class State(
    val data: String? = null
)

open class로 선언된 State를 상속받는 클래스는 다른 곳에서 참조하여 상속받아 사용할 수 있습니다.

sealed class State()

하지만 sealed class해당 패키지 내에서만 자식 클래스를 상속할 수 있는 추상 클래스입니다.

추상 클래스이기 때문에 sealed class의 인스턴스를 생성할 수는 없습니다.


sealed class 선언 방법

  1. 보통 선언 방법
sealed class State

object Success : State()
object Failure : State()

  1. Nested class(중첩 클래스로 선언)
sealed class State{
    object Success : State()
    object Failure : State()
}


🚩sealed class의 특징


자식 클래스 제한

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의 장점은 자식 클래스를 패키지 내에 제한하는 sealed class의 특징에서 나옵니다.
when을 사용할 때, 하위 클래스들의 케이스를 else 없이 커버할 수 있습니다.

이런 예시를 한번 생각해 봅시다.

어떠한 상태를 표현하려고 합니다

  1. 성공
  2. 로딩중
  3. 에러

이러한 경우에 따라 메시지를 출력하는 예제를 만들어 보겠습니다.

abstract class로 구현

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로 구현

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 VS enum class

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

profile
android_developer

2개의 댓글

comment-user-thumbnail
2022년 6월 25일

크리스마스 씰은 없나요?

1개의 답글