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
클래스는 자기 자신이 추상 클래스이고, 자신을 상속받는 여러 서브 클래스들을 가질 수 있다. 이를 사용하면 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
클래스의 서브 클래스 각각에 대해 여러 개의 인스턴스 생성 가능sealed
클래스의 계층을 생성할 수 있음Sealed Class 는 Enum Class 의 확장판과도 같다. 제한적인 계층관계를 효과적으로 표현할 수 있고, 이에 따라 when
문 사용 시 효과적으로 사용할 수 있다.