Kotlin Sealed 클래스


Sealed 클래스 (Sealed classes)

sealed 클래스와 인터페이스는 상속을 보다 효율적으로 관리 할 수 있는 제한된 클래스 계층을 나타낸다. Sealed의 뜻은 봉인이라는 뜻으로 Enum 클래스의 확장 형태로 Sealed 클래스는 클래스들을 묶은 클래스이라고 할 수 있다. Sealed 클래스의 모든 직접 서브 클래스는 컴파일시에 인식된다. Sealed 클래스 모듈들을 컴파일이 되고 나서는 다른 서브 클래스는 표시되지 않는다. 예를 들어, 써드파트의 클라이언트 코드에서는 Sealed 클래스를 확장 할 수 없다. 따라서 Sealed 클래스의 각 인스턴스는 이 클래스가 컴파일 될 때 인식되는 제한된 집합 유형이 있다.

sealed 인터페이스의 구현에 대해서도 동일하게 동작한다. Sealed 인터페이스를 가진 모듈이 컴파일된 후에는 새로운 구현은 할 수 없다.

어떤 의미에서의 sealed 클래스는 열거형 클래스와 비슷하다. 열거형의 값 집합도 제한되지만 각 열거형 정수는 단일 인스턴스로만 존재하는 반면, Sealed 클래스의 서브 클래스는 각각 고유한 상태를 가진 여러 인스턴스를 가질 수 있다.

예를 들어, 라이브러리의 API에 대해 생각해 보겠다. 라이브러리 사용자가 던질 수 있는 예외를 처리 할 수 ​​있도록 하기 위해 에러 클래스가 포함되어 있을 수 있다. 이러한 예외 클래스 계층에 공용 API에 표현되는 인터페이스 또는 추상 클래스가 포함되어 있으면 클라이언트 코드에서 이를 구현 또는 확장하는 것을 막을 수는 없다. 그러나 라이브러리는 라이브러리의 외부에서 선언된 오류를 인식하지 않기 때문에 자신의 클래스에서 오류를 일관되게 처리 할 수 ​​없다. 오류 클래스의 Sealed 계층 구조는 라이브러리의 작성자는 가능한 모든 오류 유형을 확실하게 파악할 수 있으며, 다른 오류 유형은 나중에 표현되지 않도록 보장할 수 있다.

Sealed 클래스 또는 인터페이스를 선언하려면 이름 앞에 sealed 한정자를 붙인다.

sealed interface Error

sealed class IOError(): Error

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

object RuntimeError : Error

sealed 클래스는 그 자체로 추상적이며, 직접 인스턴스화할 수 없으며 추상 멤버를 가질 수 있다.

sealed 클래스의 생성자는 기본적으로 보호된(private) 또는 비공개(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
}

직접적인 서브 클래스의 위치 (Location of direct subclasses)

sealed 클래스와 인터페이스의 직접적인 서브 클래스는 동일한 패키지에 선언되어야 한다. 최상위 수준이거나 다른 명명된 클래스, 명명된 인터페이스 또는 명명된 객체 안에 중첩될 수 있다. 하위 클래스는 Kotlin의 일반 상속 규칙과 호환되는 한 가시성을 가질 수 있다.

sealed 클래스의 하위 클래스에는 적절한 정규화된 이름이 있어야 한다. 로컬 객체도 익명의 객체도 될 수 없다.

enum 클래스는 sealed 클래스 (및 기타 클래스)를 확장 할 수 없지만 sealed 인터페이스를 구현 할 수 있다.

이러한 제한은 간접적인 서브 클래스에는 적용되지 않는다. sealed 클래스의 직접적인 서브 클래스가 sealed 것으로 표시되어 있지 않은 경우 한정자에서 허용되는 방식으로 확장 할 수 있다.

sealed interface Error // has implementations only in same package and module

sealed class IOError(): Error // extended only in same package and module
open class CustomError(): Error // can be extended wherever it's visible

멀티 플랫폼 프로젝트의 상속 (Inheritance in multiplatform projects)

멀티 플랫폼 프로젝트는 상속 제한이 하나가 더 있다. sealed 클래스의 직접 서브 클래스는 동일한 소스 집합에 존재해야 한다. 이것은 expect 수식과 실제 규정되지 않은 sealed 클래스에 적용된다.

sealed 클래스가 공통 소스 세트에서 expect로 선언 된 플랫폼 소스 세트에 실제 구현이있는 경우 expect 버전과 실제 버전 모두에서 소스 세트에 서브 클래스를 포함 할 수 있다. 또한 계층 구조를 사용하는 경우 expect 선언과 actual 선언 사이의 임의의 소스 세트에 서브 클래스를 만들 수 있다.

멀티 플랫폼 프로젝트의 계층 구조에 대해 자세히 알아보길 바란다.

sealed 클래스와 when 표현식 (Sealed classes and when expression)

sealed 클래스를 사용하는 주된 이점은 when을 사용할 때이다. 명령문이 모든 경우를 포함하는지 확인할 수 있는 경우는 명령문에 else 절을 ​​추가할 필요가 없다. 그러나 이는 when을 명령문이 아닌 표현식(결과 사용)으로 사용하는 경우에만 작동한다.

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}") }
    RuntimeError ->  { println("Runtime error") }
    // the `else` clause is not required because all the cases are covered
}

멀티 플랫폼 프로젝트의 공통 코드에서 예상되는 sealed 클래스에 대한 식에 여전히 else 분기를 필요로 하는 경우. 이는 실제 플랫폼 구현의 서브 클래스가 공통 코드로 인식되지 않기 때문에 발생한다.

참조