enum class, sealed class

sumi Yoo·2022년 9월 23일
0

enum class

열거형의 준말이다.
상태를 구분하기 위한 객체들을 이름을 붙여 여러개 생성해두고 그중 하나의 상태를 선택하여 나타내기 위한 클래스이다.

  • enum 클래스 안의 객체들은 관행적으로 상수를 나타낼 때 사용하는 대문자로 기술한다.
  • enum의 객체들은 고유한 속성을 가질 수 있다.
  • enum에 생성자를 만들어 속성을 받도록 하면 객체를 선언할 때 속성도 설정할 수 있다.
  • 일반 클래스 처럼 함수도 추가할 수 있다.
  • 이때는 객체의 선언이 끝나는 위치에 세미콜론을 추가한 후 함수를 기술한다.
enum class Color(val number: Int) {
    RED(1), BLUE(2), GREEN(3);
    
    fun isRed() = this == Color.RED
}

fun main() {
    
    var color = Color.BLUE
    
    println(color)
    println(color.number)
    println(color.isRed())
    
}

이때 비교할 대상은 State 객체 자기 자신이므로 this로 기재한다.

주의할 점

Enum은 Red라는 하위 객체를 Singleton처럼 1개만 생성할 수 있고 복수의 객체는 생성할 수는 없다. 같은 맥락으로 enum 클래스의 속성을 변경할 수 없다.

val aa = Colorr.BLUE
    val bb = Colorr.BLUE
    aa.r = 120
    println(aa.r)
    println(bb.r)
    
// 120
// 120

aa와 bb는 같은 객체이기 때문에 값이 같이 바뀌어 버린다.. 이러한 단점을 극복할 수 있는게 sealed class이다. sealed class는 1개 이상의 객체를 생성할 수 있다.

sealed class

Super class를 상속받는 Child 클래스의 종류 제한하는 특성을 갖고 있는 클래스다.

클래스 앞에 sealed 키워드를 붙이면 이 클래스는 abstract 클래스가 된다. 그리고 하위 클래스가 이 클래스를 상속하도록 선언하면 된다.

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()
}

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()

둘 다 같은 의미이다.
괄호로 묶지 않고 작성했을 땐 이렇게 사용한다.

sealed class Result

open class Success(val message: String) : Result()
class Error(val code: Int, val message: String) : Result()

class Status: Result()
class Inside: Success("Status")

fun eval(result: Result): String = when(result) {
    is Status -> "in progress"
    is Success -> result.message
    is Error -> result.message
}

fun main() {
    val result = Success("Good!")
    val msg = eval(result)
    println(msg)
}
  • 클래스 앞에 sealed keyword를 붙여 정의합니다.
  • Sealed class는 abstract 클래스로, 객체로 생성할 수 없습니다.
  • Sealed class의 생성자는 private입니다. public으로 설정할 수 없습니다.
  • Sealed class와 그 하위 클래스는 동일한 파일에 정의되어야 합니다. 서로 다른 파일에서 정의할 수 없습니다.
  • 하위 클래스는 class, data class, object class으로 정의할 수 있습니다.

sealed class로 부터 얻는 이점

하위 클래스를 제한해서 얻는 이점 중에 하나는 when을 사용할 때다. when은 모든 케이스에 대해서 처리가 되어야 하기 때문에 else 구문이 꼭 들어가야 한다. 하지만 Sealed class를 사용하면 컴파일 시점에 하위 클래스들이 정해져있기 때문에, 모든 하위 클래스에 대한 케이스를 구현하면 else 구문을 추가하지 않을 수 있다.

String으로 타입을 체크하여 font를 결정하는 코드입니다. 이 코드의 when에는 else가 필요하다. 없으면 error가 난다.

val color = "red"
val font = when (color) {
    "red" -> {
        "Noto Sans"
    }
    "green" -> {
        "Open Sanse"
    }
    else -> {
        "Arial"
    }
}

만약 blue에 대한 것을 추가하여 확장하고 싶다면 when에 다음과 같이 코드를 추가해야 한다. 하지만 실수로 blue에 대한 코드를 추가하지 않았다고 했을때 컴파일이 잘 될까? 잘 된다! 그 이유는 else 구문이 있기 때문이다. 하지만 이상한 결과를 출력하게 되겠죠..

이제 이 상황을 sealed class 로 똑같이 구현을 해보면 else 가 없어도 error가 나지는 않지만, 분명 Blue 클래스를 갖고 있는것을 알기 때문에 when 안에 Blue에 대한 코드를 추가하지 않으면 컴파일 에러가 발생한다.

sealed class Color {
    object Red: Color()
    object Green: Color()
    object Blue: Color()
}

val color : Color = Color.Red
val font = when (color) {
    is Color.Red -> {
        "Noto Sans"
    }
    is Color.Green -> {
        "Open Sans"
    }
    // compile error!
}

정리하면, sealed class는 컴파일 시점에 존재할 수 있는 클래스 타입이 정해져 있기 때문에 when을 사용할 때 else를 사용하지 않아도 된다. else를 사용하지 않았기 때문에 기능을 확장할 때 위의 예제와 같이 실수로 코드를 추가하지 않는 일이 발생하지 않게 된다.

Sealed class와 Enum의 차이점

코틀린에서 제공하는 Enum도 하위 클래스의 타입들이 정해져있다는 점에서 Sealed class와 비슷하다. 가장 큰 차이점은 Enum은 Single instance만 만들 수 있는 반면에 Sealed class는 객체를 여러개 생성할 수 있다는 것이다.

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

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 클래스의 서브 클래스 각각에 대해 여러 개의 인스턴스 생성 가능하기 때문에 상태값을 유동적으로 변경할 수 있다.

참고자료
참고자료

0개의 댓글

관련 채용 정보