출처 : https://blog.kotlin-academy.com/enum-vs-sealed-class-which-one-to-choose-dc92ce7a4df5
TL;DR : Enum 에는 valueOf, values 또는 enumValues와 같은 지원 기능이 있어 반복하거나 직렬화하기가 더 쉽습니다. 클래스와 마찬가지로 사용자 정의 메소드를 갖거나 데이터를 보유할 수 있지만 항상 enum value 당 하나씩 있습니다. 상수 값 집합을 나타내기에 완벽합니다. Sealed class 는 인스턴스에 특정한 데이터를 보유할 수 있습니다. 그것들은 구체적인 서브클래스 세트로 메시지나 클래스를 표현하는 데 완벽합니다.
Enum
가능한 옵션의 지속적인 집합을 나타내야 할 때 고전적인 선택은 Enum을 사용하는 것이었습니다. 예를 들어, 당사 웹사이트가 구체적인 지불 방법 집합을 제공하는 경우 다음 열거형 클래스를 사용하여 서비스에서 이를 나타낼 수 있습니다.
enum class PaymentOption {
CASH,
CARD,
TRANSFER
}
Enum은 항상 항목별 값을 보유할 수 있습니다.
import java.math.BigDecimal
enum class PaymentOption {
CASH,
CARD,
TRANSFER;
var commission: BigDecimal = BigDecimal.ZERO
}
fun main() {
val c1 = PaymentOption.CARD
val c2 = PaymentOption.CARD
print(c1 == c2) // true, because it is the same object
c1.commission = BigDecimal.TEN
print(c2.commission) // 10
val t = PaymentOption.TRANSFER
print(t.commission) // 0, because `commission` is per-item
}
이러한 값을 항목별로 정적으로 변경 가능하게 만드는 것은 좋지 않습니다. 이 기능은 종종 각 항목마다 일정한 값을 추가하는 데 사용됩니다. 이러한 상수 값은 기본 생성자를 사용하여 각 항목 생성 중에 첨부할 수 있습니다.
import java.math.BigDecimal
enum class PaymentOption(val commission: BigDecimal) {
CASH(BigDecimal.ONE),
CARD(BigDecimal.TEN),
TRANSFER(BigDecimal.ZERO)
}
fun main() {
println(PaymentOption.CARD.commission) // 10
println(PaymentOption.TRANSFER.commission) // 0
val paymentOption: PaymentOption = PaymentOption.values().random()
println(paymentOption.commission) // 0, 1 or 10
}
Kotlin enum 에는 메서드가 있을 수도 있습니다. 구현도 항목별로 다릅니다. 그것들을 정의할 때 enum class 자체(여기서 PaymentOption과 같은)는 추상 메서드를 정의해야 하며 각 항목은 이를 재정의해야 합니다.
enum class PaymentOption {
CASH {
override fun startPayment(transaction: TransactionData) {
showCashPaimentInfo(transaction)
}
},
CARD {
override fun startPayment(transaction: TransactionData) {
moveToCardPaymentPage(transaction)
}
},
TRANSFER {
override fun startPayment(transaction: TransactionData) {
showMoneyTransferInfo()
setupPaymentWatcher(transaction)
}
};
abstract fun startPayment(transaction: TransactionData)
}
이 옵션은 functional type 의 기본 생성자 매개변수를 사용하는 것이 더 편리하기 때문에 거의 사용되지 않습니다.
enum class PaymentOption(val startPayment: (TransactionData)->Unit) {
CASH(::showCashPaimentInfo),
CARD(::moveToCardPaymentPage),
TRANSFER({
showMoneyTransferInfo()
setupPaymentWatcher(it)
})
}
훨씬 더 편리한 방법은 extension function 을 정의하는 것입니다.
enum class PaymentOption {
CASH,
CARD,
TRANSFER
}
fun PaymentOption.startPayment(transaction: TransactionData) {
when (this) {
PaymentOption.CASH -> showCashPaimentInfo(transaction)
PaymentOption.CARD -> moveToCardPaymentPage(transaction)
PaymentOption.TRANSFER -> {
showMoneyTransferInfo()
setupPaymentWatcher(transaction)
}
}
}
enum class PaymentOption {
CASH,
CARD,
TRANSFER
}
inline fun <reified T: Enum<T>> printEnumValues() {
for(value in enumValues<T>()) {
println(value)
}
}
fun main() {
val options: Array<PaymentOption> = PaymentOption.values()
println(options.map { it.name }) // [CASH, CARD, TRANSFER]
val option: PaymentOption = PaymentOption.valueOf("CARD")
println(option) // CARD
val options2: Array<PaymentOption> = enumValues<PaymentOption>()
println(options2.map { it.name }) // [CASH, CARD, TRANSFER]
val option2: PaymentOption = enumValueOf<PaymentOption>("CARD")
println(option2) // CARD
printEnumValues<PaymentOption>()
}
따라서 enum 값에 대한 반복은 쉽고 직렬화/역직렬화는 간단하고 효율적이며(일반적으로 이름으로만 표시됨) 직렬화를 위해 대부분의 라이브러리(예: Gson, Jackson, Kotlin Serialization 등)에서 자동으로 지원됩니다. 그들은 또한 ordinal 를 가지며 자동으로 toString, hashCode 및 equals를 구현합니다. 결과적으로 enum 은 상수 값의 구체적인 집합을 나타내기에 완벽합니다.
Sealed class
구체적인 값 그룹을 나타내는 또 다른 방법은 sealed class 입니다. sealed class 는 동일한 파일에 정의된 구체적인 수의 하위 클래스가 있는 추상 클래스입니다.
Sealed 수정자가 하는 일은 파일 외부에서 이 클래스의 다른 하위 클래스를 정의하는 것이 불가능하다는 것입니다. 덕분에 단일 파일을 분석하는 것만으로도 sealed class 의 하위 클래스가 무엇인지 확신할 수 있습니다. Kotlin 컴파일러는 옵션을 제안할 수 있는 경우와 같은 일부 상황에서도 이를 알고 있으며 모든 가능성이 포함되어 있음을 이해합니다. (out 한정자를 이해하려면 이 문서를 참조하십시오. 여기에 설명된 모든 유형의 하위 유형은 Nothing 입니다.)
sealed class Response<out R>
class Success<R>(val value: R): Response<R>()
class Failure(val error: Throwable): Response<Nothing>()
fun handle(response: Response<String>) {
val text = when (response) {
is Success -> "Success, data are: " + response.value
is Failure -> "Error"
}
print(text)
}
Sealed classe 는 sum type(category 이론의 부산물)을 표현하는 데 완벽합니다. 가장 일반적인 것과 같은 대안(alternatives) 집합 : 왼쪽 또는 오른쪽이 있지만, 둘다 가질수는 없습니다.
sealed class Either<out L, out R>
class Left<L>: Either<L, Nothing>()
class Right<R>: Either<Nothing, R>()
우리의 응용 프로그램에서는 대체 클래스(alternative classe) 집합을 처리할 때 sealed class 를 사용합니다. 예를 들어 API는 표시되어야 하는 광고의 종류를 알려줍니다.
sealed class AdView
object FacebookAd: AdView()
object GoogleAd: AdView()
class OwnAd(val text: String, val imgUrl: String): AdView()
sealed class PaymentOption {
object Cash
object Card
object Transfer
}
enum 이 이러한 경우엔 더 적절하기 때문에 이런 방식으로 sealed class 를 사용하지는 않습니다.
enum 에 비해 sealed class 의 장점은 하위 클래스가 인스턴스별 데이터를 보유할 수 있다는 것입니다. 예를 들어 선택한 지불 옵션에 대해 애플리케이션의 다른 부분에 알릴 때 선택한 지불 type 과 나중에 처리하는 데 필요한 지불 관련 데이터를 모두 전달할 수 있습니다.
import java.math.BigDecimal
sealed class Payment
data class CashPayment(val amount: BigDecimal, val pointId: Int): Payment()
data class CardPayment(val amount: BigDecimal, val orderId: Int): Payment()
data class BankTransfer(val amount: BigDecimal, val orderId: Int): Payment()
fun process(payment: Payment) {
when (payment) {
is CashPayment -> {
showPaymentInfo(payment.amount, payment.pointId)
}
is CardPayment -> {
moveToCardPaiment(payment.amount, payment.orderId)
}
is BankTransfer -> {
val bankTransferRepo = BankTransferRepo()
val transferDetails = bankTransferRepo.getDetails()
displayTransferInfo(payment.amount, transferDetails)
bankTransferRepo.setUpPaymentWathcher(payment.orderId, payment.amount, transferDetails)
}
}
}
sealed class 를 사용하기 좋은 경우는 모든 종류의 이벤트 또는 메시지를 나타내는 것입니다. 왜냐하면 우리는 그것이 어떤 이벤트인지, 각 이벤트가 가진 데이터가 무엇인지 둘 다 알기 때문입니다.
Summary
- enum class 는 구체적인 값 집합을 나타내는 반면 sealed class 는 구체적인 클래스 집합을 나타냅니다. 이러한 클래스는 객체 선언이 될 수 있으므로 enum 대신 어느 정도 sealed class 를 사용할 수 있지만 그 반대는 불가능합니다.
- enum class 의 장점은 즉시 직렬화 및 역직렬화할 수 있다는 것입니다. 또한 values() 및 valueOf 메서드가 있습니다. enumValues 및 enumValueOf 함수를 사용하여 type 별로 enum 값을 얻을 수도 있습니다. enum 에는 순서가 있으며 각 항목별로 일정한 데이터를 보유할 수 있습니다. 가능한 값의 상수 집합을 나타내는 데 완벽합니다.
- sealed class 의 장점은 인스턴스별 데이터를 보유할 수 있다는 것입니다. 각 항목은 클래스 또는 객체(객체 선언을 사용하여 생성)일 수 있습니다. 이들은 대체 클래스 세트(sum type, 부산물)를 나타냅니다. 성공 또는 실패인 결과, Node 의 Leaf 또는 Tree 또는 list, object, string, boolean, int 또는 null인 JSON 값 과 같은 대안을 정의하는 데 유용합니다. 또한 발생할 수 있는 일련의 이벤트 또는 메시지를 정의하는 데도 유용합니다.