봉인된 클래스(Seald Class)
코틀린에서 enum과 함께 두 번째로 잘 사용하지 않는 기능을 한 번 정리해보려고 합니다. 비록 장점과 정의 부분에 있어서 미숙한 부분이 있지만 이해해 주길 바랍니다.
들어가기 전 항상 이런 개념적인 내용을 볼 때 드는 생각은 왜 써야 하는가?이다.
왜? 어디에? 언제?
SealedClass의 장점은 when절을 쓸 때 else구문을 사용할 필요가 없다. when절이 값을 반환해야 한다면 else문이 없을 경우 컴파일 에러가 날 수 있다. 하지만 조건분기들을 SealedClass에 넣어놓는다면 컴파일이 가능하다.
이게 장점?이라고 반문할 수 있지만 생각보다 큰 장점이다.
조건 분기에서 else문을 지운다는 의미는 내가 원한 조건 이외의 것을 고려할 필요가 없다는 뜻이고 훨씬 안정적인 조건분기를 만들 수 있다는 의미이다.
또한 라이브러리의 오류 클래스에서도 장점을 보인다.
라이브러리는 사용하면서 발생할 수 있는 오류에 대해 정의해 놓은 오류 클래스를 포함하고 있을 가능성이 크다. 이러한 오류 클래스의 계층 구조에 공개 API에서 볼 수 있는 인터페이스나 추상 클래스가 포함되어 있다면, 클라이언트(사용자) 코드에서 별도로 구현하거나 확장하는 것을 막을 수 없다.
반면에 라이브러리는 외부에서 정의된 에러에 대해 알 방법이 없으므로 자체 클래스로 일관되게 처리할 수 없다. 라이브러리에서 일관되게 처리할 수 없다는 것은 라이브러리를 쓸 필요가 없다는 의미다. 하지만 오류 클래스를 봉인된 클래스(Sealed Class)로 봉인된 계층구조를 만들게 된다면 라이브러리 작성자는 가능한 모든 오류 유형을 알고 있으며, 나중에 다른 유형이 나타나지 않도록 할 수 있다.(else문을 지우는 것과 같은 의미)
개념
실드 클래스란 값이 정해진 타입만 필요할 때, 클래스 계층의 제한을 표기하기 위해 사용한다.
enum클래스와 비슷하다고 생각할 수 있다. enum타입도 값 집합이 제한되어 있지만, 각 enum 상수는 단지 한 개의 인스턴스로 존재하는 반면, 실드 클래스의 하위 클래스는 상태가 있는 여러 인스턴스로 존재할 수 있다.
타입을 하나로 묶어준다는 점에서 직렬화와 비슷하다고 볼 수 있다.
실드 클래스는 하위 클래스를 가질 수 있지만 모든 하위클래스는 반드시 실드 클래스와 같은 파일에 선언해야 한다.
sealed interface Error
sealed class IOError(): Error
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
object RuntimeError : Error
실드 클래스의 특징은 다음가 같다.
- 자체 추상이므로, 인스턴스를 생성할 수 없으며, abstract멤버를 가질 수 있다.
- private, protected이 아닌 생성자를 허용하지 않는다. (ex. public)
- 실드 클래스의 하위 클래스를 확장하는 클래스(간접 상속)는 어디든 위치할 수 있다. 반드시 같은 파일일 필요는 없다.
sealed class IOError {
// protected
constructor() { /*...*/ }
// private
private constructor(description: String): this() { /*...*/ }
// public constructor(code: Int): this() {} // Error: public and internal are not allowed
}
하위 클래스의 선언 위치
봉인된 클래스 및 인터페이스를 바로 선언받는 하위클래스는 동일한 패키지에서 선언되어야 한다. 하위 클래스는 최상위 수준이거나, 다른 클래스, 인터페이스 또는 개체의 수에 관계없이 중첩될 수 있다. 또한 하위 클래스는 kotlin의 일반 상속 규칙과 호환되는 한 모든 가시성을 가질 수 있다.
봉인된 클래스의 하위 클래스에는 적절한 정규화된 이름이 있어야 한다. 로컬 또는 익명 개체일 수 없다.
이러한 제한은 간접 하위클래스에는 적용되지 않는다. 봉인된 클래스를 바로 상속받는 하위 클래스가 봉인된 것으로 표시되지 않은 경우 수정자가 허용하는 모든 방식으로 확장할 수 있다.
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
실드 클래스의 이점은 when 식에서 드러난다.
fun eval(expr: Expr): Double = when(expr){
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
모든 경우를 다룰 수 있다면 else문은 필요 없다.
그래서 어떻게 쓰는데?
아쉽게도 이번 게시글에선 어떤 방식으로 사용해야 하는지 다뤄보지 않았다. 여지껏 정리한 것만 봐선 열거형 클래스와 크게 다르지 않아 보인다.
열거형 클래스를 정리한 이후, 열거형 클래스와 비교하며 실 사용 예제를 다뤄보도록 하겠다.
참고
Sealed classes and interfaces | Kotlin
kotlinlang.org
'코틀린' 카테고리의 다른 글
중첩 클래스와 내부 클래스 (0) | 2023.09.18 |
---|---|
제네릭 ( in & out) (0) | 2023.09.14 |
데이터 클래스(Data Class) (0) | 2023.03.20 |
확장(Extensions) (0) | 2023.03.07 |
가시성 수식어(Visibility modifiers) (0) | 2023.02.02 |