Sealed classes and interfaces
Sealed classes와 interfaces는 상속에 대해 더 많은 제어를 제공하는 제한된 클래스 계층을 대표한다.
실드 클래스의 모든 직접적인 서브 클래스들은 컴파일 시에 알려진다.
실드 클래스가 정의된 모듈과 패키지 밖에서는 서브클래스가 나타나지 않을 것이다.
제 3자는 실드클래스를 그들의 코드에서 확장하지 못할 것이다.
따라서 실드 클래스의 각 인스턴스는 이 클래스가 컴파일되었을 때 알려진 제한된 집합으로부터 타입을 갖는다.
같은 일은 실드 인터페이스와 그 구현들에서도 일어난다. 실드 인터페이스가 있는 모듈이 한번 컴파일되면 새로운 구현은 나타나지 못한다.
어떤 의미에서 실드 클래스는 enum 클래스와 비슷하다.:
enum 타입의 값들의 집합 역시 제한되어있지만 각 enum 상수는 단일 인스턴스로만 존재한다. 반면, 실드 클래스의 서브클래스는 여러 개의 각각 고유의 상태를 가진 인스턴스를 가질 수 있다.
예로는 library의 API를 들 수 있다. 라이브러리 사용자가 발생할 수 있는 오류를 처리하도록 error 클래스들을 포함할 가능성이 높다. 만약 그러한 에러 클래스들의 계층구조가 공용 API에서 볼 수 있는 인터페이스나 추상 클래스들을 포함한다면, 클라이언트 코드에서 그것들을 구현하거나 확장하는 것을 막지 못한다.하지만 라이브러리가 라이브러리 바깥에서 선언된 에러들을 알지 못한다면, 라이브러리는 그것들을 자체 클래스로 지속적으로 처리할 수 없다. 에러 클래스들의 감춰진 계층구조로 라이브러리 제작자들은 그들이 모든 가능한 에러 타입들을 알고 있고 다른 유형은 나중에 나타날 수 없음을 확신할 수 있다.
// sealed 클래스나 인터페이스를 선언하기 위해 이름 앞에 sealed를 붙인다.
// sealed 클래스는 저절로 abstract이고, 직접적으로 인스턴스화될 수 없고 abstract 멤버들을 가질 수 있다.
sealed interface Error
sealed class IOError(): Error
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
object RuntimeError : Error
object 키워드
object는
- 싱글턴 클래스로 만들거나
- 익명 클래스 객체를 생성할 때 사용한다.
-싱글턴 클래스 예
object CarFactory { // 이 객체는 한 번만 생성됨.
val cars = ...
fun makeCar(...): Car {
....
}
}
class Car(...){}
fun main(){
val car = CarFactory.makeCar(...) // 메소드 접근 가능
println(CarFactory.cars) // 변수에 접근 가능
}
-익명 클래스 객체 생성 예
interface Animal {
fun walk(): String
}
fun wake(animal: Animal) = println(animal.walk)
wake(object: Animal{
override fun walk() = "Walking slowly"
})
//object: Animal{...}은 익명 객체. Animal을 상속 받음. 클래스 이름은 없고 {} 안에 구현부 정의
sealed class IOError {
constructor() { /*...*/ } // sealed 클래스의 생성자의 visibility는 기본적으로 protected이다.
private constructor(description: String): this() { /*...*/ } // private 도 가능하지만
// public constructor(code: Int): this() {} // public과 internal은 불가능하다. 에러 발생.
}
Location of direct subclasses
실드 클래스들 실드 인터페이스들의 직접적인 서브 클래스들은 같은 패키지 않에 선언되어야 한다. 서브 클래스들은 top-level이거나 다른 명명된 클래스, 명명된 인터페이스, 또는 명명된 객체의 수에 관계없이 중첩될 것이다. 서브클래스들은 코틀린에서 보통의 상속 규칙과 호환되는 한 어떠한 visibility라도 가질 수 있다.
실드클래스의 서브클래스들은 적절한 자격을 갖춘 이름을 가져야 한다. 로컬 객체도 익명 객체도 될 수 없다.
(enum 클래스들은 실드클래스들을 포함하여 다른 어떠한 클래스들도 확장하지 못하지만 실드 인터페이스를 구현할 수 있다.)
이런 제한들은 간접적인 서브클래스들에는 적용되지 않는다. 만약 실드 클래스의 직접적인 서브클래스가 sealed로 마크되어 있지 않다면, 수식어가 허용하는 대로 어느 방법으로든지 확장될 수 있다.
sealed interface Error // 같은 패키지와 모듈 내에서만 구현을 가질 수 있다.
sealed class IOError(): Error // 같은 패키지와 모듈 내에서만 확장될 수 있다.
open class CustomError(): Error // 보이는 모든 위치에서 확장 가능하다.
Inheritance in multiplatform projects
멀티플랫폼 프로젝트에서는 상속 제한이 하나 더 있다: 실드 클래스들의 직접적인 서브 클래스들은 같은 소스 세트에 있어야 한다. expect와 actual 수식어가 없는 실드 클래스들에 적용된다.
만약 실드 클래스가 공통 소스 세트에서 expect로 선언되어 있고 플랫폼 소스 세트들에서 actual 구현을 가지고 있다면 expect와 actual 버전들 모두 그들의 소스 세트들에 서브 클래스들을 가질 수 있다. 게다가 hierarchical structure을 사용한다면, expect 선언과 actual 사이의 모든 소스 세트에서 서브클래스들을 생성할 수 있다.
expect: 기대하는 클래스 등을 정의한다. 공통 부분에서 사용한다.
actual: 의존하는 플랫폼의 코드를 작성한다. 의존 부분에서 사용한다.
(출처: https://www.devkuma.com/docs/kotlin/etc/ )
Sealed classes and when expression
실드 클래스를 사용하는 것의 핵심적인 이점은 when expression을 사용할 때 나타난다.
만약 statement가 모든 경우를 다루는 것을 확인하는 것이 가능하다면, 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` 절을 쓸 필요가 없다.
}
(멀티플랫폼 프로젝트의 공통부분 코드에서 expect 실드 클래스들에 대한 when expression들은 여전히 else가 필요하다. actual 플랫폼 구현의 서브클래스들을 공통 부분 코드에서는 모르기 때문임.)
읽으면 좋은 블로그(https://kotlinworld.com/165)
'Kotlin 공부' 카테고리의 다른 글
kotlinlang.org 내가 보려고 정리 - Sequences (0) | 2023.08.08 |
---|---|
kotlinlang.org 내가 보려고 정리 - Object expressions and declarations (0) | 2023.08.04 |
kotlinlang.org 내가 보려고 정리 - Data classes (0) | 2023.08.02 |
kotlinlang.org 내가 보려고 정리 - Null safety (0) | 2023.08.01 |
kotlinlang.org 내가 보려고 정리 - Classes (0) | 2023.07.31 |