👩💻 오늘의 할 일
오늘은 Kotlin의 Sealed Class에 대해 알아보겠습니다. 이전 동아리 면접 질문에서 Sealed Class와 Enum Class의 차이점을 물어봤는데 아예 몰라서 대답을 못했던 경험이 있어서 이 부분에 대해서도 한번 알아보고 가겠습니다. 멋사에서 Kotlin 수업을 들을 때 한 번 들었었는데 좀 더 자세히 알아보려고 합니다.
🔥 수업 시간에 배운 Sealed Class
- Sealed Class는 자기 자신을 상속받은 클래스들을 모아 관리하는 개념입니다.
- Kotlin의 클래스와 그 멤버 함수들은 final이기 때문에 상속될 수 없습니다.
- sealed class는 그 자체로 추상적이기 때문에 open 키워드로 메서드를 상속할 수 있습니다
fun main() {
val obj1 = NumberClass.OneClass(100, 200)
val obj2 = NumberClass.TwoClass(300)
val obj3 = NumberClass.ThreeClass(100, 11.11)
checkNumber(obj1)
checkNumber(obj2)
checkNumber(obj3)
}
sealed class NumberClass{
open fun numberMethod1(){
println("NumberClass의 numberMethod1")
}
class OneClass(var a1:Int, var a2:Int) : NumberClass()
class TwoClass(var a1:Int) : NumberClass(){
override fun numberMethod1() {
super.numberMethod1()
println("TwoClass의 numberMethod1")
}
}
class ThreeClass(var a1:Int, var a2:Double):NumberClass()
}
fun checkNumber(t1:NumberClass){
when(t1){
is NumberClass.OneClass -> {
println("OneClass입니다")
println(t1.a1)
println(t1.a2)
t1.numberMethod1()
}
is NumberClass.TwoClass -> {
println("TwoClass입니다")
println(t1.a1)
t1.numberMethod1()
}
is NumberClass.ThreeClass -> {
println("ThreeClass 입니다")
println(t1.a1)
println(t1.a2)
t1.numberMethod1()
}
}
}
👩🏫 중간 정리
여기까지 느낀 바로는 Enum Class와 달리 sealed class는 상수가 아닌 하위 클래스들을 관리하기 위한 class 이고 이름에서 유추할 수 있듯이 sealed class를 상속받는 하위 클래스는 다른 클래스를 상속받을 수 없게 봉인! 하는 역할을 하는 것 같습니다. 이제 좀 더 알아보겠습니다.
📌 sealed class의 등장 배경
일반 추상(abstract) Class를 상속받는 하위 Class들은 Compiler가 상위 Class를 상속 받은 하위 Class의 종류를 알지 못합니다.
abstract class PersonState
class Running : PersonState()
class Walking : PersonState()
class Idle : PersonState()
fun getStateMessage(personState: PersonState): String{
return when(personState){
is Running -> "Peroson is running"
is Walking -> "Peroson is Walking"
is Idle -> "Peroson is Idle"
}
}
위의 코드는 else branch를 추가하라는 오류를 만듭니다.
🤔 왜 else branch를 추가하라고 했을까요?
- 컴파일러가 PersonSate를 상속받는 하위 클래스의 종류를 알지 못하기 때문입니다.
- 그럼 이 케이스가 왜 문제가 될까요?
- 위 코드에서 Idel에 대한 분기를 지우면 오류 없이 컴파일되는 것을 확인할 수 있습니다.
- 이럴 경우 실제 사용되는 애플리케이션에 올라갔다면 Idle에 대한 처리가 되지 않아 애플리케이션은 제대로 동작하지 않습니다.
- 즉, 코드에 문제가 있어도 컴파일 단에서 잡아내지 못하고 오류도 발생하지 않아 문제를 잡아내기가 매우 힘들어집니다.
🏳🌈 Sealed Class는 이런 문제를 쉽게 해결해 줍니다
📌Sealed Class 란
- 추상 클래스(abstract class)로 상속받는 하위 클래스의 종류를 제한하는 특성을 가지고 있습니다.
- 이를 통해 컴파일러가 sealed class의 자식 클래스가 어떤 것이 있는지 알 수 있습니다.
📜 예제 1
위의 예제를 그대로 추상 클래스에서 sealed class로 변경해 보겠습니다.
sealed class PersonState
class Running : PersonState()
class Walking : PersonState()
class Idle : PersonState()
fun getStateMessage(personState: PersonState): String{
return when(personState){
is Running -> "Peroson is running"
is Walking -> "Peroson is Walking"
is Idle -> "Peroson is Walking"
}
}
sealed class PersonState의 자식 클래스에는 Running, Walking, Idle 세 가지만 가지고 있는 것을 알고 있기 때문에 일반 추상 클래스를 사용할 때와 달리 else branch에 대한 오류가 발생하지 않습니다.
📜 예제 2
위의 예제에서 또 다른 클래스를 하나 추가하고 분기 처리하지 않으면 어떻게 될까요?
- sealed class인 Rest에 대한 처리가 안 돼있기 때문에 컴퍼 일러는 오류를 출력합니다.
- Rest에 대한 분기를 처리하면 오류가 사라지는 것을 확인할 수 있습니다.
그런데 sealed class를 상속받은 class들에 왜 주의 표시가 되어 있을까요?🤔
'sealed' subclass has no state and no overridden 'equals()’
해석: sealed class의 subclass가 상태가 없고 equals( )를 오버라이드 하지 않습니다.
이는 상태(변수)가 있거나 equals를 override 할 때만 sealed class로 상속받으라는 말입니다.
Convert sealed subclass to object를 클릭하면 다음과 같이 코드가 변경됩니다.
Data object
2023년 release 된 kotlin 1.8.20 version에서 추가된 기능으로 일반 object와 동일하지만 내부적으로 toString(), euquals(), hasCode()를 구현하고 있습니다. 데이터 클래스와 유사하지만 object의 기능을 포함하는 것 같네요!
👩💻 sealed class의 다양한 서브 클래스 타입
sealed class의 서브 클래스들은 각자의 특성에 따라 다양한 생성자를 가질 수 있습니다.
object는 데이터를 받아 상태가 변하지 않고 한 번만 객체가 생성되는 타입일 경우 사용합니다. 하지만 object 또한 IDE에서 data object Type으로 변경할 것을 권장하네요. 점점 코틀린의 버전이 업데이트되는 만큼 stable 되는 기능들이 많아져서 이 부분 또한 놓치지 않고 공부해야 할 것 같습니다.
sealed interface
생성자를 필요로 하지 않는다면 Sealed Class의 생성자를 가지고 있지 않은 interface 도 사용할 수 있습니다.
🙇♂️ 후기
이렇게 한번 쭉 Sealed Class에 대해 알아봤는데 아직은 멀고 어렵게 느껴지는 것 같습니다. 실제로 프로젝트에서 사용해봐야 감이 좀 잡힐 것 같은데 어떻게 적용할지도 감이 잘 안 잡히네요😭 그래도 꾸준히 알아보면서 추후에 프로젝트 리팩토링 할 때 한번 사용해 보도록 노력해야겠습니다!
'KOTLIN' 카테고리의 다른 글
Kotlin Value Class With Project Valhalla (1) | 2024.11.22 |
---|---|
Coroutine SharedFlow (0) | 2024.03.09 |
StateFlow가 중복된 값을 반환하지 않는 이유(DistinctUntilChanged) (0) | 2024.03.08 |
[Kotlin] LiveData 대신 StateFlow 사용하기 (1) | 2024.01.15 |
[Kotlin] Coroutine Flow (0) | 2024.01.15 |