Sealed Class, Enum Class

#Kotlin

Sealed Class

Kotlin Doc - Sealed classes and interfaces

1. 봉인된 클래스

Sealed Class는 통제된 클래스 계층 구조를 제공하기 위한 클래스이다.

통제된 클래스 계층 구조..?
이 말의 의미를 보려면 일반 클래스의 상속을 먼저 살펴보는게 좋다.

1-1. 봉인되지 않은 클래스

open class Fruit
class Apple: Fruit()
class Orange: Fruit()
class Mango: Fruit()

class Test {
	
	// Error! (else branch를 작성하라는 에러)
	fun printFruitLabel(item: Fruit): String {  
	    return when(item) {  
	        is Apple -> "I am an Apple without iOS"  
	        is Orange -> "I am an O-Orange"  
	        is Mango -> "I am a Sweet Mango"  
	    }  
	}
}

Fruit의 자식 클래스는 Apple, Orange, Mango 3종류가 있다. 과일가게에서 과일의 판매명을 프린트하기 위해 printFruitLabel()을 만들었다.

컴파일러는 Fruit의 자식 클래스에 어떤것들이 있는지 알지 못한다. Apple, Orange, Mango 말고도 더 있다면, Fruit의 자식 클래스로 Grape을 만들어서 그것을 printFruitLabel() 메서드의 파라미터로 넘겼다면? return 할 수 없는 경우도 발생한다. 컴파일러는 자식클래스를 파악할 수 없기 때문에 Grape가 있을지도 모른다고 가정해야한다.

내가 가진 과일은 3종류 뿐이고 이것만 가지고 판별해줬으면 좋겠는데. 컴파일러가 알아줬으면 좋겠다...

1-2. 봉인!

sealed class Fruit
class Apple: Fruit()
class Orange: Fruit()
class Mango: Fruit()

class Test {
	
	// Good!
	fun printFruitLabel(item: Fruit): String {  
	    return when(item) {  
	        is Apple -> "I am an Apple without iOS"  
	        is Orange -> "I am an O-Orange"  
	        is Mango -> "I am a Sweet Mango"  
	    }  
	}
}

컴파일러한테 말한다. "Fruit의 자식 클래스를 파악해둬라."고
컴파일러는 Fruit의 자식이 3종류 뿐임을 확인했고(Grape같은 클래스는 없다고 확신한다.), 모든 경우의 수를 분기하였으니 더이상의 에러는 나지 않는다.

이렇게 컴파일러가 상속관계를 파악할 수 있도록 하기위해 사용하는 것이 Sealed Class 이다.

따라서 우리도 상속관계를 파악해두고 있는게 좋다. 자식 클래스들이 여기저기 흩어져있으면 파악하기가 어렵기 때문에 하나의 파일에 부모와 자식클래스들을 정리하는것이 일반적이다. (Kotlin 1.5.0 이전에는 하나의 파일 안에 정리해두어야 했지만, 이후에는 같은 패키지 안에서는 자식 클래스를 선언할 수 있도록 하였다. 왜그랬을까...)

2. Sealed Class 특징 및 사용

2-1. 추상 클래스의 한 종류로 객체화 할 수 없다.

그러나 생성자를 가질 수 있다. 생성을 위한 용도가 아니라 자식 클래스에서 공통으로 가지는 변수같은 것을 담기 위한 생성자이다. Kotlin Doc - Sealed classes and interfaces, Constructors

2-2. 사용 이점

Kotlin sealed class의 사용방법과 특징에 대해서 알아보기 : Kotlin 1.5.0에서의 변경 사항, DevReview 님 포스트의 특징 부분에 잘 나와있다.

2-2. Use Case

상태를 관리하거나 요청-응답 관련한 사용이 많았다.
Kotlin Doc - Sealed classes and interfaces, Use case scenarios

2-3. 사용

Sealed Class 내부에 class 로 정의하거나, 외부로 빼서 사용해도 된다. (같은 패키지 내에만 있으면 된다.) 자식 클래스에서 별도의 변수나 로직을 포함하지 않으면 메모리 절약을 위해 object 객체로 생성하길 권장한다.

sealed class Fruit {
	object Apple: Fruit()
	object Orange: Fruit()
	class Mango(val isAppleMango: Boolean): Fruit()
}

Enum Class

1. Flag

Enum Class는 상수를 그룹화 하기위해 주로 사용했던 것 같다.
C 언어를 사용할 때에는 이런식으로 사용했던걸

const int EAST = 0
const int WEST = 1
const int SOUTH = 2
const int NORTH = 3

enum class를 이용해 이렇게 사용할 수 있게 되면서 어떤 것(방향)을 구분하기 위한 플래그 인지가 조금 더 명확해졌다.

enum class Direction {
	EAST, WEST, SOUTH, NORTH
}

하지만, 그저 이렇게 사용하기에는 아까운 enum class 이다.

2. enum class 안에 있는 애들은 뭐야?

EAST, WEST, SOUTH, NORTH 이것들은 뭘까?
변수라기엔 타입이 없고 클래스라기엔 class prefix같은 것들이 없다.

이것들은 object 객체이다.

Each enum constant is an object. Enum constants are separated by commas.
Since each enum is an instance of the enum class, it can be initialized as:...

2-1. enum class의 object 클래스(객체)들

Kotlin의 Enum 어디까지 써봤니?, BANZIHA104 님의 포스팅에 enum관련 변수, 함수와 사용법이 정말 잘 정리되어있다. 참고하면 도움이 될 것이다.

object 클래스가 객체화 된 애들. 그렇다면, 이 object 클래스에는 변수도, 함수도 넣을 수 있다는 의미가 된다. 더욱이 다른 클래스를 상속받을 수도 있고! 하지만 다른 object 클래스와 다른점이 있다면 부모 클래스라고 생각할 수 있는 enum class에 이 변수와 함수에 대한 부분이 정의되어 있어야 이것을 작성할 수 있게 된다.

enum class 내부에 object 클래스가 가지고 있는 공통적인 변수와 함수는 다음과 같다.

함수는 clone(), compareTo(), enumValues<Enum>(), enumValueOf<Enum>()등이 있다.