Programming Language/Kotlin

[Kotlin] Sealed Class

개발왕 금골드 2023. 2. 19. 16:15
반응형

소개

상수로 정의한 특정 값들을 enum class로 정의하여 사용할 수 있다. 그렇게 하면 엉뚱한 값이 할당되는 상황을 방지할 수 있어 개발자의 실수를 줄일 수 있다. 이런 enum class의 한계는 내부적으로 각 enum constant는 하나의 single instance를 갖기 때문에 서로 다른 객체를 가질 수 없다는 것이다.
 

 
또한, Kotlin에서 자주 사용하는 when 문을 사용할 때, enum value가 누락되었어도 어떤 오류 메시지를 발생시키지 않는다. 반면에, sealed class를 사용하여 구현하였을 경우 when 문 안에서 누락된 값을 캐치할 수 있다.

설명

  • sealed class와 interface는 상속에 대한 더 많은 제어를 제공하는 제한된 클래스 계층을 나타낸다.
  • sealed class의 모든 하위 클래스는 컴파일 시에 알려 진다.
  • sealed class가 정의된 모듈 외부에서는 다른 하위 클래스가 표시될 수 없다.
  • sealed class의 constructor는 두 가지 가시성 중 한 개를 사용할 수 있다. (protected or private)
  • sealed class는 abstract이기 때문에 직접 인스턴스를 생성할 수 없으며, abstract 멤버를 가질 수 있다.


예제

일반적으로 하나의 부모 클래스를 상속 받고 있는 여러 자식 클래스가 있을 때, 컴파일러는 부모 클래스가 어떤 자식 클래스를 두고 있는지 알 수 없다. 예를 들어, 상태 값을 정의하기 위해 클래스를 정의하고 이에 대한 메세지를 작성하기 위해 when문을 선언하였다 하자.

abstract class SomeState

class Idle: SomeState()
class Running: SomeState()
class Jumping: SoneState()

...

fun getStateMessage(state: SomeState): String {
   // else 문을 작성하라는 오류 메시지가 나온다.
   return when (state) {
      is Idle -> "is Idle"
      is Running -> "is Running"
      is Jumping -> "is Jumping"
      // else -> ""
   }
}

위 예제처럼 코드를 작성하였을 경우 컴파일러는 when문 안에 else문을 작성하라는 에러를 보여준다. else문을 작성하면 에러는 해결되지만 만약 아래와 같이 작성하는 실수를 하였을 경우 이 실수에 대해서 컴파일러는 아무런 액션을 취하지 않는다.

fun getStateMessage(state: SomeState): String {
   return when (state) {
       is Running -> "is Running"
       else -> ""
   }
}


단순히 else문을 작성하여 에러를 지우는 것은 쉽지만, 실제 방대한 양의 코드를 작성하였다고 가정하였을 때, 이러한 실수는 충분히 발생가능하며, 어떠한 에러도 발생하지 않기 때문에 디버그하기 매우 까다로울 수 있다. Kotlin에서 sealed class를 사용하면 상속 받는 자식 클래스를 제한할 수 있다. 또한, 컴파일러가 sealed class의 자식들을 알 수 있다.

sealed class SomeState

class Idle : SomeState()
class Running : SomeState()
class Jumping : SomeState()

...

fun getStateMessage(state: SomeState): String {
// 컴파일러가 seal class의 자식을 알 수 있다.
// else문을 작성하지 않아도 에러가 발생하지 않는다.
    return when (state) {
        is Idle -> "is Idle"
        is Running -> "is Running"
        is Jumping -> "is Jumping"
    }
}

object로 상속받기

sealed class를 class로 상속 받으면 warnning이 나타난다. (sealed subclass has no state no overriden equals) 이는 상태 값(변수)이 있거나 equals를 오버라이드 해야 하는 경우만 class로 상속 받으라는 뜻이다.

class Idle : SomeState() // Warnning
class Running(val speed: Int) : SomeState()
class Jumping : SomeState() {
	override fun equals(other: Any?): Boolean {
		return super.equals(other)
	}

	override fun hashCode(): Int {
		return javaClass.hashCode()
	}
}

warnning이 뜨는 이유는 상태 값이 없는 객체를 두 번 이상 메모리에 올리는 것이 낭비이기 때문이다. 따라서 class가 아닌 object로 선언하여 싱글톤 패턴을 사용하면 메모리에 한 번만 올라가고 재사용되기 때문에 낭비가 없다.

sealed class SomeState

object Idle : SomeState()
object Running : SomeState()
object Jumping : SomeState()


결론

sealed class는 여러 상황에서 유용하게 사용될 수 있다. 특히, 위에 작성된 예제와 마찬가지로 when문과 함께 사용할 때, 실수를 줄일 수 있도록 도와주는 부분과, 하나의 집합 안에서 서로 다른 객체로 선언할 수 있다는 점은 특정 부분에서 enum class보다 더 나은 확장성을 제공한다. 그러나 모든 경우에서 sealed class가 enum class의 상위호환으로 작용되는 것은 아니다. enum class는 하나의 인스턴스이기 때문에 가능한 초기화나 ordinal 같은 기능이 있고, 이를 sealed class는 제공하지 않는다.
 
아는 만큼 보인다고 가볍게 생각하고 넘어 갔던 문법이나 개념들을 쌓다 보면, 적재적소에 맞는, Kotlin 언어를 개발한 개발자들의 의도에 맞는 Kotlin 사용이 가능할 것이라고 희망한다.
 


참고자료 :
[Kotlin] Kotlin sealed class란 무엇인가?
Sealed classes | Kotlin
https://tourspace.tistory.com/467
https://medium.com/proandroiddev/understanding-kotlin-sealed-classes-65c0adad7015

반응형