코틀린 sealed class vs enum class | 공통점 과 차이점
enum class
Enum 은 Enumeration 의 약자로 열거형을 의미하고 연관되거나 관련이 있는 상수들의 집합을 표현할 때 사용한다.
물론 static 으로 선언하고 사용해도 되지만 enum 을 통해 해당 모음의 의미 전달력보다는 떨어질 것이다. 또한, 인스턴스의 생성과 삼속을 방지하여 타입 안정성이 보장된다.
예제 코드를 살펴보자
fun main() {
/**
* Enum Class
*/
val list = listOf<EnumCafe>(EnumCafe.Americano, EnumCafe.Frappe, EnumCafe.IceTea, EnumCafe.Latte)
for (item in list) {
val result = when (item) {
EnumCafe.Americano -> "아메리카노"
EnumCafe.Latte -> "라떼"
EnumCafe.Frappe -> "프라페"
EnumCafe.IceTea -> "아이스티"
}
println(result)
}
}
enum class EnumCafe(val price: Int, val menuName: String, val sugar: Int) {
Americano(3500, "아이스아메리카노", 10),
Latte(4000, "아이스라데", 25),
Frappe(5000, "프라페치노", 100),
IceTea(4500, "아이스티", 50)
}
카페에서 파는 메뉴를 모아두고 관리를 하는 것이 목적이고 각 메뉴에 따른 결과를 출력하고 있다. 우리가 여기에서 생각해봐야하는 문제는 물가상승으로 인해 변한다면? 아메리카노, 라뗴, 프라페, 아이스티의 price를 다 수정해야 한다. enum class 에 정의하면 속성값들을 바꾸는 것에 제약사항이 있다. 이외에 설탕 인자가 없는 별도의 인자를 만들수도 없고 이벤트 인자가 들어간 새로운 메뉴도 넣을 때 제약이 발생한다.
sealed class
Sealed 클래스는 추상 클래스이면서 자신을 상속받는 여러 서브 클래스들을 가질 수 있다. 이 점을 통해 enum class 의 제약사항을 해결할 수 있다.
Sealed 클래스 사용법
- 서브 클래스를 같은 파일내 선언 또는 블록안에 선언
- private 생성자만 가지고 기본적으로 abstract
예제 코드를 살펴보면,
fun main() {
/**
* Sealed Class
*/
val list = listOf<SealedCafe>(
SealedCafe.Americano(500, "아메리카노", true),
SealedCafe.Latte(500, "아메리카노"),
SealedCafe.Frappe(500, "아메리카노", "사과"),
SealedCafe.IceTea(500, "아메리카노", 100)
)
for (item in list) {
val result = when (item) {
is SealedCafe.Americano -> "아메리카노"
is SealedCafe.Latte -> "라떼"
is SealedCafe.Frappe -> "프라페"
is SealedCafe.IceTea -> "아이스티"
}
println(result)
}
}
sealed class SealedCafe {
data class Americano(val price: Int, val menuName: String, val 디카페인: Boolean) : SealedCafe()
data class Latte(val price: Int, val menuName: String) : SealedCafe()
data class Frappe(val price: Int, val menuName: String, val fruit: String) : SealedCafe()
data class IceTea(val price: Int, val menuName: String, val ice: Int) : SealedCafe()
}
enum class 와 달리 속성의 값을 변동적으로 사용할 수 있고 이종의 클래스들을 혼합해서 사용할 수 있다. when 절을 사용할 시에는 is Type 으로 체크하면 된다.
sealed class 응용편
sealed 클래스는 안드로이드와 서버간의 네트워크 통신의 성공/실패를 다루는데 아주 유용하게 사용할 수 있다.
기존의 네트워크 통신을 하고 데이터를 처리할 때 json 받아서 처리하는데 모든 네트워크 통신마다 결과를 처리하는 것을 만들어야 한다. 동일한 역할을 수행하는 코드가 늘어나고 가독성도 떨어진다.
sealed class 예제 코드를 살펴보면,
sealed class SearchResult {
data class Success(val searchList: List<String>): SearchResult()
data class Fail(val err: Exception): SearchResult()
object Progress : SearchResult()
}
fun searchResult(result: SearchResult) {
when (result) {
is Result.Success -> // 성공처리
is Result.SucFailcess -> // 실패처리
is Result.Progress -> // 진행중처리
}
}
성공/실패/처리중을 동일한 클래스로 처리하다보니 데이터를 전달 및 받는 과정이 편해진다. 이전보다는 중복코드를 줄이는 장점이 있지만 모든 네트워크 통신마다 sealed class 를 만들어야 하는 단점이 있다.
이럴때 Generic 을 사용하면 더욱 편리해진다.
sealed class SearchResultGeneric<out T: Any> {
data class Success<out T: Any>(val dataList: List<T>): SearchResultGeneric<T>()
data class Fail<out T: Any>(val err: Exception): SearchResultGeneric<T>()
object Progress : SearchResultGeneric<Nothing>()
}
fun searchResultGeneric(result: SearchResultGeneric<T>) {
when (result) {
is Result.Success -> // 성공처리
is Result.SucFailcess -> // 실패처리
is Result.Progress -> // 진행중처리
}
}
sealed 클래스를 사용할 통신에서 데이터의 타입을 받아서 처리하면 유동적으로 사용할 수 있기에 위에서 발생하는 반복적인 클래스 생성을 막을 수 있다.