[Kotlin] Sealed Class 알아보기

H43RO·2021년 10월 8일
8

Kotlin 과 친해지기

목록 보기
13/18
post-thumbnail

Enum Class 이야기

enum 은 C언어에도 존재할 만큼, 범용성이 뛰어난 녀석이다. 코틀린에도 당연하게 enum 클래스가 존재한다. 이를 활용해서 아래와 같은 동작을 구현할 수 있었다.

enum class Color(val r: Int, val g: Int, val b: Int) {
    RED(255, 0, 0), ORANGE(255, 165, 0),
    YELLOW(255, 255, 0), GREEN(0, 255, 0),
    BLUE(0, 0, 255);

    fun rgb() = (r * 256 + g) * 256 + b
}

fun getColorName(color: Color) = when (color) {
    Color.RED -> "빨강"
    Color.ORANGE -> "주황"
    Color.YELLOW -> "노랑"
    Color.GREEN -> "초록"
    Color.BLUE -> "파랑"
}

fun main() {
    println(getColorName(Color.BLUE))
}

getColorName() 을 통해서 when 문을 통해 enum 객체 각각에 대한 분기 동작을 정의해줄 수 있었다. 위 예제는 각 색상의 RGB 값을 기반으로 enum 클래스를 만들고, when 을 통해 각각의 한글 이름을 출력하는 동작을 구현해본 것이다.

그런데 갑자기 RGB 값이 조금 수정되어야 하는 상황이 발생할 수 있다. 갑자기 클라이언트가 색상들이 조금 쿨톤을 띠었으면 좋겠다고 한다. 따라서 enum 객체들 각각에 B (Blue) 값을 20씩 추가하려고 한다.

그러나 enum 클래스의 각 상수들은 싱글톤 디자인 패턴을 따르기 때문에, 단 하나의 인스턴스만 존재하게 된다. 따라서 최초에 설정한 enum 각각에 대한 상태를 변경할 수 없다. 정적인 상태를 갖게 되는 것이다. RED(255, 0, 0) 라고 RED 를 정의해뒀으면, 속성값들을 바꿀 수 없다는 이야기이다.

그리고 상속이 불가능한 형태기 때문에 enum 에 대한 서브 클래스를 생성할 수도 없다.

이 내용들을 정리하자면 다음과 같다.

제약사항

  • enum 상수들은 단 하나의 인스턴스만 가질 수 있음 (싱글톤)
    • enum 상수 정의 이후에 속성값을 변경할 수 없음
  • enum 클래스에 대해 서브 클래스를 생성할 수 없음

코틀린에서는 이러한 enum 클래스의 제약사항들을 커버할 수 있는 sealed 클래스라는 것을 제공한다.


Sealed Class

sealed 클래스는 자기 자신이 추상 클래스이고, 자신을 상속받는 여러 서브 클래스들을 가질 수 있다. 이를 사용하면 enum 클래스와 달리 상속을 지원하기 때문에, 상속을 활용한 풍부한 동작을 구현할 수 있다.

그리고 자신을 상속받는 서브 클래스의 종류를 제한할 수 있다. 왜냐하면 sealed 클래스는 다음과 같은 특성을 지니기 때문이다.

  • sealed 클래스의 서브 클래스들은 반드시 같은 파일 내에 선언되어야 함
    • 단, sealed 클래스의 서브 클래스를 상속한 클래스들은 같은 파일 내에 없어도 됨
  • sealed 클래스는 기본적으로 abstract 클래스임
  • sealed 클래스는 private 생성자만 갖게 됨

자신의 서브 클래스 또한 같은 파일 내에 선언되어야 하기 때문에, 컴파일러에게 '얘네 말고 내 자식 없어 ㅋㅋ 다른 곳 안 가봐도 돼' 하고 알려주는 것과도 같다.

아래와 같이, Color 라는 sealed 클래스를 생성해보자.

sealed class Color {
    data class Red(val r: Int, val g: Int, val b: Int) : Color()
    data class Orange(val r: Int, val g: Int, val b: Int) : Color()
    data class Yellow(val r: Int, val g: Int, val b: Int) : Color()
    data class Green(val r: Int, val g: Int, val b: Int) : Color()
    data class Blue(val r: Int, val g: Int, val b: Int) : Color()
    data class Indigo(val r: Int, val g: Int, val b: Int) : Color()
    data class Violet(val r: Int, val g: Int, val b: Int) : Color()
}

서브 클래스 각각은 각기 다른 값을 가지고 개성있게 생성될 수 있도록 되어 있다. 서브 클래스들은 class, data class, object 모두 가능하다.

다시 한 번 말하지만, 이러한 서브 클래스들은 분명 같은 파일 안에 선언해야 한다. 같은 파일 내에 있다면 아래와 같은 형태도 가능하다.

sealed class Color

data class Red(val r: Int, val g: Int, val b: Int) : Color()
data class Orange(val r: Int, val g: Int, val b: Int) : Color()
data class Yellow(val r: Int, val g: Int, val b: Int) : Color()
data class Green(val r: Int, val g: Int, val b: Int) : Color()
data class Blue(val r: Int, val g: Int, val b: Int) : Color()
data class Indigo(val r: Int, val g: Int, val b: Int) : Color()
data class Violet(val r: Int, val g: Int, val b: Int) : Color()

상태값이 바뀌지 않는 서브 클래스의 경우 object 를 사용하는 것을 권장한다.

이를 활용하여, 아래와 같은 동작이 가능하다.

fun main() {
    val color: Color = Color.Red(255, 0, 20)
    when (color) {
        is Color.Red -> println("빨강")
        is Color.Orange -> println("주황")
        is Color.Yellow -> println("노랑")
        is Color.Green -> println("초록")
        is Color.Blue -> println("파랑")
				is Color.Indigo -> println("인디고")
				is Color.Violet -> println("바이올렛")
    }
}

기존의 빨간색에 쿨톤(?)을 가미하여 B (Blue) 값을 20 늘려줄 수 있다.

또한 인스턴스를 여러 개 생성할 수 있기 때문에, 아래와 같은 동작도 가능하다. 해당 빨간색 객체가 쿨톤인지 아닌지를 감별하는 함수를 구현한 것이다.

fun isCoolToneRed(color: Color.Red) = color.b >= 20

fun main() {
    val redA = Color.Red(255, 0, 20)
    val redB = Color.Red(255, 0, 0)

    println(isCoolToneRed(redA))  // true
    println(isCoolToneRed(redB))  // false
}

Sealed Class 의 이점

  • sealed 클래스의 서브 클래스 각각에 대해 여러 개의 인스턴스 생성 가능
    • 때문에 상태값을 유동적으로 변경할 수 있음
  • sealed 클래스의 계층을 생성할 수 있음

Sealed Class 는 Enum Class 의 확장판과도 같다. 제한적인 계층관계를 효과적으로 표현할 수 있고, 이에 따라 when 문 사용 시 효과적으로 사용할 수 있다.

참고자료

https://codechacha.com/ko/kotlin-sealed-classes/

profile
어려울수록 기본에 미치고 열광하라

0개의 댓글