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 클래스는 기본적으로 자기 자신이 abstract 클래스이고, 자신을 상속받는 여러 서브 클래스들을 가질 수 있습니다. 이를 사용하면 enum 클래스와 달리 상속을 지원하기 때문에, 상속을 활용한 풍부한 동작을 구현할 수 있습니다.
그리고 자신을 상속받는 서브 클래스의 종류를 제한할 수 있습니다. 왜냐하면 sealed 클래스는 다음과 같은 특성을 지니기 때문입니다.
1. 제한된 클래스 계층
sealed class는 Kotlin에서 제한된 클래스 계층을 구성하기 위해 사용되는 특별한 종류의 클래스입니다. sealed class의 가장 큰 특징은 그 하위 클래스가 반드시 sealed class와 같은 파일 내에서 선언되어야 한다는 것입니다. 컴파일러는 sealed class와 같은 파일 내에 선언된 하위 클래스들만을 검사하므로, 모든 케이스가 명확하게 처리됩니다. 이러한 설계는 when 표현식에서 모든 가능한 케이스를 처리할 수 있도록 하며, else 케이스를 추가하지 않아도 되게 만듭니다.
즉, sealed class는 추상 클래스로써 이를 상속받는 서브 클래스는 반드시 해당 sealed class와 같은 파일 내에서만 정의되어야 합니다. 다른 파일에서 sealed class를 상속받는다면 컴파일 에러가 발생하게 됩니다.
이러한 강력한 제한 덕분에 when 절에서 else 케이스를 추가하지 않아도 되고, 개발자가 실수 없이 모든 케이스를 처리할 수 있게 됩니다. 컴파일러는 sealed class와 같은 파일에 있는 서브 클래스들만 확인하면 되기 때문에 서브 클래스의 종류가 명확해지기 때문입니다.
sealed class ApiResponse {
data class Success(val data: String) : ApiResponse()
data class Error(val error: String) : ApiResponse()
object Loading : ApiResponse()
}
fun handleResponse(response: ApiResponse) {
when (response) {
is ApiResponse.Success -> println("Success with data: ${response.data}")
is ApiResponse.Error -> println("Error: ${response.error}")
is ApiResponse.Loading -> println("Loading...")
// 'else' 케이스가 필요 없음
}
}
2. 타입 안전성
새로운 서브 클래스가 추가될 때도 안전합니다. when 조건문을 사용하는 코드에서 컴파일 타임에 모든 서브 클래스를 처리하도록 강제하기 때문에 관련된 처리 로직을 누락하는 것을 방지하게 됩니다.
sealed class ApiResponse {
data class Success(val data: String) : ApiResponse()
data class Error(val error: String) : ApiResponse()
object Loading : ApiResponse()
data class NoData : ApiResponse() // 새로운 하위 클래스 추가
}
만약 새로운 서브 클래스에 대한 로직을 처리하지 않았다면 컴파일 타임에 에러가 발생하게 됩니다. 예를 들어, ApiResponse에 NoData라는 새로운 서브 클래스를 추가했을 경우, 기존의 when 조건문에서 NoData를 처리하지 않으면 컴파일 에러가 발생합니다. 이를 통해 개발자는 모든 케이스를 명확하게 처리하도록 유도됩니다.
즉, 개발자는 이러한 에러를 컴파일 타임에 해결할 수 있게 되고 런타임 에러를 피할 수 있게 됩니다.
fun handleResponse(response: ApiResponse) {
when (response) {
is ApiResponse.Success -> println("Success with data: ${response.data}")
is ApiResponse.Error -> println("Error: ${response.error}")
is ApiResponse.Loading -> println("Loading...")
is ApiResponse.NoData -> println("No data available") // 'NoData' 케이스를 추가
}
}
3. 하위 클래스 타입차이 (class, data class, object)
sealed class의 서브 클래스는 class, data class, 또는 object로 선언할 수 있으며, 각 타입은 다른 목적을 가집니다.
- class와 data class
- 하위 클래스가 상태 변수를 가지고 있을 경우, class 또는 data class로 선언됩니다. data class는 데이터의 저장과 처리에 중점을 두며, class는 보다 복잡한 로직과 상태 관리에 적합합니다.
- object: object
- 싱글턴 인스턴스를 생성하므로, 상태 변수가 없는 경우에 사용됩니다. object를 사용하면 객체 생성 비용을 절약하고, 메모리 사용을 최적화할 수 있습니다.
이러한 차이는 sealed class를 사용하여 다양한 타입의 하위 클래스를 유연하게 정의할 수 있게 해 줍니다. 예를 들어, ApiResponse에서 Success와 Error는 데이터를 담는 data class로, Loading은 상태를 나타내는데 충분한 object로 선언됩니다. 서브 클래스의 기능을 고려해서 적합한 타입을 선언하면 됩니다.
sealed class ApiResponse {
data class Success(val data: String) : ApiResponse() // data class 선언
data class Error(val error: String) : ApiResponse() // data class 선언
object Loading : ApiResponse() // object 선언
}
안드로이드 코틀린의 Sealed Class 정리
sealed class는 제한된 클래스 계층을 통해 런타임 오류를 줄이고, 타입 안전성을 높이며, 다양한 타입의 하위 클래스를 유연하게 정의할 수 있습니다. enum class의 한계를 넘어서서, 서브 클래스 각각에 대해 여러개의 인스턴스가 생성이 가능하며, 상태값을 유동적으로 변경할 수 있습니다. 따라서, 각 상태별로 다른 데이터 타입이나 추가적인 정보를 필요로 하는 경우에 sealed class를 활용할 수 있습니다.
구현하려는 기능에 가장 적합한 방법을 선택하여 사용하면, 개발 과정에서의 유지보수성과 가독성이 크게 향상될 것입니다.
Sealed Class 는 Enum Class 의 확장판과도 같습니다. 제한적인 계층관계를 효과적으로 표현할 수 있고, 이에 따라 when 문 사용 시 효과적으로 사용할 수 있습니다.
참고자료
https://velog.io/@haero_kim/Kotlin-Sealed-Class-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0
[Kotlin] Sealed Class 알아보기
Enum Class 의 확장판, Sealed Class 의 개념
velog.io
[안드로이드 코틀린 Sealed class] 특성 및 코드 예제 #2
안드로이드 코틀린 Sealed Class의 3가지 특성 안녕하세요. 지난 포스팅에서는 Kotlin의 enum class 및 sealed class의 사용 이유에 대해서 살펴보았습니다. 오늘은 sealed class의 특성을 제한된 클래스 계층,
dev-inventory.com
https://velog.io/@ho-taek/Kotlin-Sealed-Class%EB%9E%80
[Kotlin] Sealed Class란
! 프로젝트 진행하면서 코드를 좀더 간결하게 짜고 싶은 마음에 다시 코틀린 공부를 시작하고 있다.(미리미리좀 해놓을걸...) 앱잼도 끝났고 릴리즈 및 버전 업에도 쓰일만한 것들을 위주로 공부
velog.io
'Kotlin' 카테고리의 다른 글
[Kotlin] Object 키워드 (with companion object) (0) | 2024.11.02 |
---|---|
[Kotlin] Data Class란? (3) | 2024.11.02 |
[Kotlin] 늦은 초기화 (lateinit, by lazy) (0) | 2023.08.22 |
[Kotlin] 필드(Field)와 프로퍼티(Property) (0) | 2023.08.22 |
[Kotlin] 범위 지정 함수(apply, with, let, also, run) (0) | 2023.07.22 |