본문 바로가기
Kotlin

[Kotlin] Sealed Class, Interface 사용법 (1) - Sealed Class, Interface란?

by 노력남자 2023. 6. 12.
반응형

이번 포스팅에선 코틀린 공식 문서에서 sealed class와 interface를 설명한 것을 번역해보려고 한다.

 

요즘 sealed class와 interface에 관심이 생겨서 정리하게 됐다.

 

이전에 포스팅한 Exception 대신 Sealed Class를 사용하자 글도 시간있으면 한 번 보는 걸 추천한다.

 

Sealed classes and interfaces

 

sealed class와 interface는 상속에 대한 더 많은 제어를 제공하는 제한된 클래스 계층을 나타냅니다. sealed class의 모든 직접 하위 클래스는 컴파일 시간에 알려져 있습니다. 다른 하위 클래스는 모듈 및 패키지 외부에 나타날 수 없습니다. 예를 들어, 제3자 클라이언트는 당신의 sealed class를 자신의 코드에서 확장할 수 없습니다. 따라서 sealed class의 각 인스턴스는 이 클래스가 컴파일될 때 알려진 한정된 집합의 타입을 가지게 됩니다.

 

동일한 원리가 sealed interface와 그 구현에도 적용됩니다. 한 번 sealed interface가 포함된 모듈이 컴파일되면 새로운 구현이 나타날 수 없습니다.

어느 정도로 보면, sealed class는 enum class와 유사합니다. enum 유형의 값 집합도 제한되지만, 각 enum 상수는 단일 인스턴스로만 존재하며 sealed class의 하위 클래스는 여러 인스턴스를 가질 수 있으며 각각 자체 상태를 가지게 됩니다.

 

예를 들어, 라이브러리의 API를 고려해보겠습니다. 이 API에는 라이브러리 사용자가 라이브러리에서 발생시킬 수 있는 오류를 처리할 수 있도록 하는 오류 클래스가 포함될 것입니다. 이러한 오류 클래스의 계층에는 공개 API에서 볼 수 있는 인터페이스나 추상 클래스가 포함될 수 있으므로 클라이언트 코드에서 이를 구현하거나 확장하는 것을 방지할 수 있는 방법은 없습니다. 그러나 라이브러리는 자체 클래스와 일관되게 처리할 수 없기 때문에 라이브러리 외부에서 선언된 오류를 알지 못합니다. sealed 오류 클래스의 계층 구조를 사용하면 라이브러리 작성자는 모든 가능한 오류 유형을 알고 있으며 나중에 다른 오류 유형이 나타나지 않는 것을 확신할 수 있습니다.

 

sealed class 또는 interface를 선언하려면 이름 앞에 sealed 수정자를 붙입니다.

 

sealed interface Error

sealed class IOError(): Error

class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()

object RuntimeError : Error

 

sealed class는 자체적으로 abstract이며, 직접적으로 인스턴스화될 수 없으며 abstract 멤버를 가질 수 있습니다.

sealed class의 생성자는 기본적으로 protected 또는 private 중 하나의 가시성을 가질 수 있습니다.

 

sealed class IOError {
    constructor() { /*...*/ } // protected by default
    private constructor(description: String): this() { /*...*/ } // private is OK
    // public constructor(code: Int): this() {} // Error: public and internal are not allowed
}

 

직접적인 하위 클래스의 위치

 

sealed class와 interface의 직접적인 하위 클래스는 동일한 패키지에서 선언되어야 합니다. 이 하위 클래스는 다른 이름을 가진 class, interface 또는 객체 내에 상위 수준 또는 중첩될 수 있습니다. 하위 클래스는 일반적인 Kotlin 상속 규칙과 호환되는 한 가시성을 가질 수 있습니다.

sealed class의 하위 클래스는 적절한 정규화된 이름을 가져야 합니다. 로컬 객체나 익명 객체는 하위 클래스로 사용될 수 없습니다.

 

enum class는 sealed class(뿐만 아니라 다른 class도)를 상속할 수 없지만 sealed interface를 구현할 수는 있습니다.

 

이러한 제한은 간접적인 하위 클래스에는 적용되지 않습니다. sealed class의 직접적인 하위 클래스가 sealed로 표시되지 않은 경우, 해당 class의 수정자가 허용하는 방식으로 확장할 수 있습니다.

 

sealed interface Error // 다른 패키지나 모듈에서는 구현을 가질 수 없습니다.

sealed class IOError(): Error // 동일한 패키지와 모듈에서만 확장될 수 있습니다.
open class CustomError(): Error // 보이는 곳이라면 어디에서든 확장될 수 있습니다.

 

다중 플랫폼 프로젝트에서의 상속

 

다중 플랫폼 프로젝트에서 더 많은 상속 제한 사항이 있습니다: sealed 클래스의 직접적인 하위 클래스는 동일한 소스 세트에 있어야 합니다. 이는 expect와 actual 수정자가 없는 sealed 클래스에 적용됩니다.

만약 sealed class가 공통 소스 세트에서 expect로 선언되고 플랫폼 소스 세트에서 실제 구현이 있는 경우, expect 및 actual 버전 모두 소스 세트에서 하위 클래스를 가질 수 있습니다. 더욱이, 계층 구조를 사용하는 경우, expect 및 actual 선언 사이의 모든 소스 세트에서 하위 클래스를 생성할 수 있습니다.

다중 플랫폼 프로젝트의 계층 구조에 대해 자세히 알아보세요.


sealed class와 when 표현식

 

sealed class를 사용하는 주요 이점은 when 표현식에서 사용할 때 나타납니다. 모든 경우를 커버하는 것을 확인할 수 있다면, 해당 문에 else 절을 추가할 필요가 없습니다.

 

fun log(e: Error) = when(e) {
    is FileReadError -> { println("Error while reading file ${e.file}") }
    is DatabaseError -> { println("Error while reading from database ${e.source}") }
    is RuntimeError ->  { println("Runtime error") }
    // 모든 경우가 커버되었기 때문에 else 절은 필요하지 않습니다.
}

 

다중 플랫폼 프로젝트의 공통 코드에서 기대되는 sealed class에 대한 when 표현식은 여전히 else 분기가 필요합니다. 이는 실제 플랫폼 구현의 하위 클래스가 공통 코드에서 알려지지 않기 때문입니다.

 

References

 

https://kotlinlang.org/docs/sealed-classes.html#sealed-classes-and-when-expression

반응형

댓글