코틀린 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 클래스 사용법

예제 코드를 살펴보면,

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 클래스를 사용할 통신에서 데이터의 타입을 받아서 처리하면 유동적으로 사용할 수 있기에 위에서 발생하는 반복적인 클래스 생성을 막을 수 있다.

제너릭에 대한 이해가 필요하다면 여기를 눌러주세요



Related Posts