Sealed class가 만들어진 이유

 

하나의 부모 class 안의 여러 자식 class 존재 한다고 했을 때 컴파일러는 부모 class를 상속 받은 자식 class들이 있는지 알지 못한다

 

예를 들어 보면 3개의 상태 종류로 api성공  /  api에러 / network 에러를 다음과 같이 표현 할 수 있다

abstract class NetworkState {
    
    class ApiSuccess : NetworkState()

    class ApiError : NetworkState()

    class NetworkError : NetworkState()

}

class 상속 예제

 

각 state 별로 상태 메시지를 얻고 싶다고 가정했을 때 이를 위해 getStateNetwork함수를 만들어보자

fun getStateMessage(networkState: NetworkState): String {
    return when (networkState) {
        is ApiSuccess -> "ApiSuccess"
        is ApiError -> "ApiError"
        is NetworkError -> "NetworkError"
    }
}

getStateMessage 함수

 

그러면 다음과 같이 else branch를 추가하라는 오류를 만든다

when 오류

왜 else branch를 추가를 해야되냐면 컴파일러가 NetworkState를 상속받는 하위 클래스의 종류를 알지 못하기 때문이다

 

단순해 보이지만 여기서 만약 ApiError에 대한 분기를 지우게 된다면 그러면 다음과 같이 오류 없이 컴파일 되는것을 볼수 있다

 

apiError 분기 지우기

 

만약 위의 코드가 실제로 적용되어 있다면 ApiError에 대한 처리가 되어 있지 않아 문제를 잡아내기가 힘들다

 

NetworkState를 사용하는 부분이 한곳이라면 그나마 다행이지만 대부분은 그렇지 않다.

 

즉 위의 코드는 오류를 내뱉을 수 있고 관리도 되지 않은 코드가 되어버린다.

 

그래서 그것에 대한 대안이 sealed class이다 

sealed class란 추상 클래스로 상속받는 자식 클래스의 종류를 제한하는 특성을 가지고 있다. 즉 컴파일러에서 sealed class의 자식 클래스가 어떤 것 인지 알 수 있다

 

sealed class NetworkState {

    class ApiSuccess : NetworkState()

    class ApiError : NetworkState()

    class NetworkError : NetworkState()

}

seled class 예제

 

다음 그림처럼 오류가 발생하지 않는다 이유는 컴파일러에서 sealed class NetworkState의 자식 클래스 ApiSuccess , ApiError , NetworkError 세가지만 알고 있기 때문이다 즉 when에서 else branch를 사용하지 않고도 필요한 메시지만 수신할 수 있게 된다

 

sealed class를 이용한 getStateMessage

 

여기서 앱을 확장해서 NetworkState에 UnknownError를 추가한다고 했을때 어떤 일이 일어나는지 알아보자

이때 컴파일러는 오류를 발생 시킨다 sealed class인 UnknownError에 대한 처리가 안되어 있기 때문이다.

sealed class를 상속 받은 것에 대한 처리가 없을 경우

 

하지만 위의 그림에서 약간의 이상함을 느꼇을 것이다 왜 상속받은 class들은 주의 표시가 되어 있을까?

 

sealed class 상속 warning

 

해석 : sealed class의 subclass 상태가 없고 equals()를 오버라이드 하지도 않는다

 

즉 상태(변수)가 있거나 equals를 override 할 경우에만 class로 상속받으라는 말이다 그 이외에는 메모리 절약을 위해 object를 이용한다

 

위의 코드를 다음과 같이 변경하게 되면 warning이 사라진다. 

 

object로 상속받기

 

object는 싱글톤 패턴으로 한번만 메모리에 올라가고 재사용된다 따라서 상태가 없는 경우 객체를 두 번 이상 생성하여 메모리에 올리는 것은 메모리를 낭비하는 것이다. 따라서 object로 바꿀 경우 warning이 사라지게 된다.

 

 

Sealed class의 특징

 

같은 패키지의 자식 클래스만 상속 가능

컴파일러가 모든 패키지를 돌면서 자식을 찾는 것은 리소스를 많이 소모하는 작업이다 따라서 sealed class는 자식 클래스에 대한 선언을 같은 패키지 내로 제한한다

 

package com.giosis.util.qdrive.singapore.util

sealed class NetworkState 

sealed class는 같은 패키지에서만 상속 가능하다.

 

com.giosis.util.qdrive.singapore.setting안에 NetworkState를 상속 받는 클래스를 선언하려고 하면 다음과 같이 오류가 생성된다.

sealed class는 같은 패키지에 있는 자식 클래스에서만 상속 가능하다.

 

'kotlin 개인노트' 카테고리의 다른 글

(Kotlin) for문은 이제 그만  (0) 2022.06.24

1번 소스

val newList = ArrayList<RowItem>()

for (rowItem in originalRowItem) {
    if (rowItem.isClicked) {
        newList.add(rowItem)
    }
}

2번 소스

val newList = ArrayList<RowItem>()

newList.addAll(
    originalRowItem.filter {
        it.isClicked
    }
)

저는 요즘 1번 소스를 2번 소스로 고치고 있습니다.

 

그 이유는 코틀린이 함수형 프로그래밍 언어이기 때문입니다.

 

함수형 프로그래밍 언어에서 For 반복문을 지양하는 이유

 

1. 명령형 vs 선언형

2. 원자성 유지

3. 추상화를 통한 안정성 추구

 

 

 

1.명령형 프로그래밍 vs 선언형 프로그래밍

 

명령형은 어떻게 해야하는지에 초점이 맞춰진 프로그래밍 

선언형은 무엇을 해야하는지에 초점을 맞추는 프로그래밍 방식

 

명령형 " 어떻게 데이터를 넣을까?"
선언형 "무슨 데이터를 넣을까?"

1번째 그림이 반복문을 돌면서 어떻게 데이터를 넣을지 되어 있는 코드라면

2번째 글은 어떤 데이터를 넣을 것인지에 대해 초점을 맞춘 코드입니다.

 

요즘 트렌드는 "선언형 프로그래밍이 더 낫다" 라고 많은 개발자 분들이 말하지만..

저의 개인적인 생각으로는 익숙한 코드가 더 잘 읽히는 법이라 가독성 부분은 개인차가 있다고 생각합니다.

 

 

2.원자성 유지

kotlin에서는 매번 새로운 불변형 List를 넘기는 식으로 관리를 합니다.

만약 반복문안에서 MutableList를 이용해서 조작하면 원자성이 깨질 수도 있습니다.

 

 

원자성이란?

원자성이란 완전하고 견고한 상태 혹은 아예 실패하는 실패 원자성 을 의미합니다.

쉽게 말해 성공하면 깔끔하게 성공하거나, 실패하면 완전히 실패하는 것을 말하는데 상태가 변해있으면 원자성을 유지하지 못한 것이라고 할 수 있습니다.

 

예시)

 

val aList = mutableListOf<Int>()
val bList = listOf(2,3,0,4)
try {
    bList.forEach{
        aList.add(12/it)
    }
} catch (e: java.lang.Exception) {

}
Log.e("result",aList.toString())

결과 : aList = [6,4]

 

val aList = mutableListOf<Int>()
val bList = listOf(1,2,0,4)
try {
    aList.addAll(
        bList.map {
            12 / it
        }
    )
} catch (e: java.lang.Exception) {

}
Log.e("result",aList.toString())

결과 : aList = []

 

실패했는데 1번째 코드의 경우 aList가 변해 있지만 2번째 코드의 경우는 변해있지 않습니다

반복문을 사용하지 않고 불변형 객체를 넘기는 것으로 실패의 영향을 남기지 않을 수 있습니다.

 

 

3.추상화를 통한 안정성 추구

 

반복문에서는 종류조건,비교 조건, 반복시점 등 한가지만 살짝 바뀌어도 깨지기 쉬습니다.

반복문을 사용을 하기 보다는 추상화된 함수를 검증 후 재사용하는 편이 더 좋습니다.

결과적으로 언어에서 제공한 검증된 API를 사용하는 편이 훨씬 안정된다는 의미입니다. 

 

 

'kotlin 개인노트' 카테고리의 다른 글

Kotlin Sealed Class 란??  (0) 2022.08.09

+ Recent posts