Sealed class 란?

쓰리원·2022년 6월 23일
0

data class

목록 보기
1/1
post-thumbnail

1. enum Class 란?

Sealed class 알아보기 전에 Enum Class에 대해서 알아볼 필요가 있습니다. 코틀린에서 enum은 열거형을 나타내는 특수한 클래스 타입입니다. enum 클래스는 고정된 수의 상수 객체를 정의하고, 각각이 enum 클래스의 인스턴스입니다. 이는 여러 상황에서 사용할 수 있는 미리 정의된 상수 집합을 제공할 수 있습니다.

2. enum 클래스의 사용 예

1. 코틀린의 enum 클래스는 values()와 valueOf()

1. values(): enum 클래스의 모든 상수를 배열로 반환합니다.

2. valueOf(String): 주어진 이름에 해당하는 enum 상수를 반환합니다. 이름이 일치하는 enum 상수가 없으면 IllegalArgumentException이 발생합니다.

enum class Color {
    RED, BLUE, GREEN
}

이 경우, Color.RED, Color.BLUE, Color.GREEN은 Color 타입의 세 가지 가능한 값입니다.

  1. values() 메서드는 enum class의 모든 상수를 배열로 반환합니다. 이 메서드는 enum class의 모든 상수를 반복하거나 특정 작업을 수행할 때 유용합니다. 예를 들면,
for (color in Color.values()) {
    println(color.name)
}

위의 코드는 모든 색상의 이름을 출력합니다: RED, BLUE, GREEN.

  1. valueOf(String) 메서드는 문자열 이름과 일치하는 enum 상수를 반환합니다. 이 메서드는 문자열을 enum 상수로 변환할 때 유용합니다. 예를 들면,
val colorName = "BLUE"
val color = Color.valueOf(colorName)
println(color) // BLUE

위의 코드는 "BLUE"라는 문자열을 Color.BLUE라는 enum 상수로 변환하고 출력합니다.

valueOf(String) 메서드를 사용할 때는 주의가 필요합니다. 일치하는 이름의 enum 상수가 없으면 IllegalArgumentException이 발생합니다. 예를 들어, 다음 코드를 살펴보겠습니다.

val colorName = "PURPLE"
val color = Color.valueOf(colorName) // 이 부분에서 IllegalArgumentException이 발생합니다.
println(color)

"PURPLE"이라는 이름은 Color enum에 없기 때문에 Color.valueOf("PURPLE")는 IllegalArgumentException을 발생시킵니다. 이 예외는 enum에 없는 이름을 사용하려고 시도했다는 것을 알려주는 런타임 예외입니다.

이런 문제를 피하려면 enumValues()와 firstOrNull { }를 함께 사용하여 문자열을 안전하게 enum 상수로 변환할 수 있습니다.

예를 들면,

val colorName = "PURPLE"
val color = enumValues<Color>().firstOrNull { it.name == colorName }
println(color ?: "No such color") // No such color

enumValues().firstOrNull { it.name == colorName } 코드는 Color enum의 모든 상수를 검색하고 이름이 "PURPLE"인 첫 번째 상수를 반환합니다. 그러한 상수가 없으면 null을 반환합니다. 그래서 println(color ?: "No such color")는 color가 null인 경우 "No such color"를 출력합니다.

2. enum 상수는 고유한 인스턴스이기 때문에 속성(property)

예를 들어, 각 색상에 해당하는 RGB 값을 속성으로 가진 enum 클래스를 다음과 같이 정의할 수 있습니다.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

val button: Button = findViewById(R.id.my_button)
button.setBackgroundColor(Color.RED.rgb)

이를 통해, 특정 UI 요소의 색상을 설정할 때 다음과 같이 사용할 수 있습니다. 이렇게 enum을 사용하면, 색상 값들을 한 곳에서 관리할 수 있어 유지보수가 용이합니다. 또한, 코드의 가독성을 높여 줍니다.

3. enum 클래스는 메서드를 포함할 수도 있습니다.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF);
    
    fun printRgb() {
        println(rgb)
    }
}

Color.RED.printRgb()  // 콘솔에 "16711680" 출력

위와 같이 enum이 메서드를 포함하고 있다면, 이 메서드는 각 enum 상수에 대해 호출될 수 있습니다. 예를 들어, 다음과 같이 사용할 수 있습니다. 이렇게 enum을 사용하면, 관련된 상수와 메서드를 함께 그룹화하여 코드의 구조를 개선하고 가독성을 높일 수 있습니다.

위의 기능들로 인해, 코틀린의 enum 클래스는 매우 강력하며 다양한 경우에 활용될 수 있습니다.

3. Enum Class 한계

  1. 불변성: enum class는 각 상수를 싱글톤으로 관리하기 때문에, 최초에 설정한 각 enum 상수의 상태는 변경할 수 없습니다. 이는 enum 상수가 애플리케이션의 전체 생명주기 동안 일관된 동작을 보장합니다.
  1. 상속 제한: enum class는 서브클래스를 생성할 수 없습니다. 이는 각 enum 상수가 해당 enum 클래스의 단일 인스턴스임을 보장합니다.

이러한 제약 사항들은 enum class가 안전하고 예측 가능한 방식으로 동작하도록 돕지만, 더 복잡한 경우에는 한계가 있을 수 있습니다. 이럴 때 코틀린의 sealed class가 도움이 될 수 있습니다.

4. sealed class 란?

sealed class는 제한된 클래스 계층을 정의하는데 사용됩니다. 즉, sealed class는 특정 수의 하위 클래스만을 가질 수 있습니다. 이를 통해 더 유연한 동작과 복잡한 상태를 표현할 수 있습니다.

1. enum 클래스의 불변성과 sealed class

enum 클래스의 각 상수는 한 번 초기화되면 변경할 수 없습니다. 이는 애플리케이션의 전체 수명 동안 일관된 동작을 보장합니다. 아래에 이를 설명하는 간단한 예시를 보여드리겠습니다.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

fun main() {
    println(Color.RED.rgb) // 출력: 16711680
}

위의 예제에서 코틀린의 enum 클래스는 특정 타입의 상수 집합을 정의하는데 사용됩니다. 여기서 각 enum 상수는 최초 선언 시에 고정된 데이터만 가질 수 있습니다. 예를 들어 Color enum 클래스에서 각 색상은 Int 타입의 rgb 값을 가집니다. 이는 고정된 값으로, 한 번 설정되면 변경할 수 없습니다. 즉, Color.RED.rgb의 값은 초기에 한 번 설정되며, 이후에는 변경할 수 없습니다.

그런데 이런 불변성은 동적인 상태를 다루는데 제한적일 수 있습니다. 이 때문에 Kotlin은 Sealed 클래스라는 또 다른 기능을 제공합니다. Sealed 클래스는 값의 타입이 한정된 집합에서만 가능하도록 클래스 계층을 정의하는 방법을 제공합니다. 이는 enum 클래스와 비슷하지만, 각 클래스는 상태를 가질 수 있고, 서브 클래스를 가질 수 있습니다. 아래에 Sealed 클래스를 사용한 예제를 보여드리겠습니다.

sealed class UiState {
    object Loading : UiState()
    data class Success(val data: String) : UiState()
    data class Error(val error: Throwable) : UiState()
}

fun main() {
    val uiState: UiState = UiState.Success("Loaded data")

    when (uiState) {
        is UiState.Loading -> println("Loading...")
        is UiState.Success -> println("Success: ${uiState.data}")
        is UiState.Error -> println("Error: ${uiState.error}")
    }
}

위의 예제에서, UiState는 서로 다른 3개의 상태를 표현하는 Sealed 클래스입니다. 각 상태는 서로 다른 데이터를 가질 수 있습니다. 예를 들어 UiState sealed 클래스에서 Success는 String 타입의 데이터를, Error는 Throwable 타입의 에러를 가집니다. 이런 데이터는 동적으로 변경될 수 있습니다.

sealed class는 when 표현식과 함께 사용될 때 특히 강력합니다. when 표현식이 모든 sealed class 하위 클래스를 처리하면 컴파일러는 else 절이 필요하지 않다고 판단합니다. 이는 컴파일 시 모든 가능한 경우를 처리하도록 강제하므로 더 안전한 코드를 작성할 수 있습니다.

sealed class Result {
    data class Success(val data: Any): Result()
    data class Error(val error: Exception): Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> handleSuccess(result.data)
        is Result.Error -> handleError(result.error)
    }
}

이렇게 sealed class를 사용하면, enum class의 제약 사항을 해결하면서 동시에 안전하고 예측 가능한 동작을 보장할 수 있습니다.

2. Enum 클래스는 상속을 허용하지 않는다.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

// 다음은 허용되지 않습니다:
class ExtendedColor: Color // 컴파일 에러

이것은 enum 클래스의 본질적인 특성 때문입니다. enum 클래스는 프로그램의 실행 동안 변경되지 않는 상수 값을 표현합니다. 이러한 상수 값은 enum 클래스의 각 요소(예: RED, GREEN, BLUE)가 클래스의 단일 인스턴스임을 보장하는 것이 중요합니다. 이러한 인스턴스는 프로그램의 생명주기 동안 변경되지 않으며, 각 인스턴스는 고유한 상태를 가질 수 없습니다.

따라서 이러한 제약 사항으로 인해, enum 클래스는 서브 클래스를 생성할 수 없습니다. 즉, enum 클래스는 클래스 계층을 형성할 수 없습니다. 이것은 enum 클래스의 한계이지만, 이것이 enum 클래스의 주요 목적과 맞춰진 설계입니다.

그러나 이러한 제한 사항 때문에 enum 클래스를 사용할 수 없는 경우가 있습니다. 예를 들어, 상태에 따라 다양한 동작을 해야하는 경우 등입니다. 이럴 때는 sealed 클래스를 사용하는 것이 더 적합합니다. sealed 클래스는 클래스 계층을 형성할 수 있으며, 각 서브 클래스는 상태를 가질 수 있습니다.

profile
가장 아름다운 정답은 서로의 협업안에 있다.

0개의 댓글