#고민리스트 #Kotlin
- 정리한 날짜 : #2025-07-08
### 상황 : sealed interface 알게 됐다.
`sealed class`를 자주 활용했는데, 이번에 `sealed interface`를 알게 됐다. 어떤 차이가 있고 어떻게 활용할 수 있을지 비교해봤다.
#### sealed 키워드란?
`sealed`는 말 그대로 봉인이다. 동일한 패키지가 아니라면 상속받을 수 없도록 제한한다. `sealed class`, `sealed interface`는 말 그대로 동일한 패키지만 상속 및 구현하도록 제한하는 역할을 한다.
### 차이점 : `class` 단일 상속과 `interface` 다중 구현에서 차이
#### `class` 단일 상속
`kotlin`은 단일 상속만 허용한다. 그래서 다중 상속시 다음처럼 예외가 발생한다.
![[스크린샷 2025-07-08 오후 2.31.59.png]]
따라서 `sealed class`로는 단일 상속만 가능하다.
![[스크린샷 2025-07-08 오후 2.32.47.png]]
#### `interface` 다중 구현
반면 `kotlin`은 다중 구현을 허용한다.
![[스크린샷 2025-07-08 오후 2.38.56.png]]
그래서 `sealed interface` 사용했을 때 다중 구현을 활용할 수 있다.
![[스크린샷 2025-07-08 오후 2.40.50.png]]
### 차이점에 대한 동작 차이 : 기능 응집과 객체 응집
응집 타겟에 따라 구현 방법을 달리해볼 수 있다고 생각한다. 기능 응집 사례인 경우 `sealed class`를 활용하고 객체 응집 사례인 경우 `sealed interface` 활용해보는게 좋은 방법이다.
#### 기능 응집 사례
```kotlin
sealed class GoodBye {
data class EnglishBye(val bye: String) : GoodBye()
data class KoreanBye(val bye: String) : GoodBye()
data class JapaneseBye(val bye: String) : GoodBye()
}
sealed class Greeting {
data class EnglishHello(val greeting: String) : Greeting()
data class KoreanHello(val greeting: String) : Greeting()
data class JapaneseHello(val greeting: String) : Greeting()
}
class Korean {
val bye: GoodBye = GoodBye.KoreanBye("잘가")
val hello: Greeting = Greeting.KoreanHello("안녕")
}
```
기능 응집은 다음 장단점이 존재한다.
- 장점
- 기능 단위로 행동을 관리할 수 있다.
- 각 기능은 각자만의 상태를 가질 수 있다.
- 단점
- 객체가 행동은 없고 상태 만 가질 수 있다.
- 행동을 추가하면 전체 영향이 발생한다.
#### 객체 응집 사례
```kotlin
sealed interface Greeting {
fun hello(): String
}
sealed interface GoodBye {
fun bye(): String
}
interface Korean: Greeting, GoodBye {
override fun hello() = "안녕"
override fun bye() = "잘가"
}
```
객체 응집은 다음 장단점이 존재한다.
- 장점
- 객체가 행동을 가지며 스스로 관리할 수 있다.
- 새로운 행동이 추가되도 기존 행동에 영향을 미치지 않는다.
- 단점
- 기능 단위 관리가 어렵다.
- 각 기능은 동일한 상태를 가져야 한다.
### 차이점에 대한 활용 방안 : 응집 대상에 따라 구현 방법이 달리한다.
#### 기능별 응집 대상
안정적인 패턴 매칭
```kotlin
fun processGreeting(greeting: Greeting) =
when (greeting) {
is Greeting.EnglishHello -> "English: ${greeting.hello}"
is Greeting.KoreanHello -> "Korean: ${greeting.hello}"
is Greeting.JapaneseHello -> "Japanese: ${greeting.hello}"
// 새로운 언어 추가 시 컴파일 에러로 누락 방지
}
```
기능별로 메타 데이터 관리가 편하다. 같은 기능이지만 서로 다른 상태를 가져야 한다면 좋은 선택이라 생각한다.
```kotlin
sealed class Greeting {
abstract val language: String
abstract val formality: FormalityLevel
data class EnglishHello(
val greeting: String,
override val language: String = "English",
override val dialect: String = "Southern"
) : Greeting()
}
```
#### 객체별 응집 대상
객체가 가지는 특성을 `interface`로 상속받아서 구현해볼 수 있는데, `entity` 선언하는 경우 해당 패키지 내부에서만 허용하고 싶을 때 활용해볼 수 있다.
![[스크린샷 2025-07-08 오후 3.25.59.png]]
아래처럼 엔티티와 도메인을 따로 관리하는 경우 재사용성을 높일 수 있다. (덕분에 도메인과 엔티티 간 정보가 다른 문제를 해결할 수 있다.)
![[스크린샷 2025-07-08 오후 3.26.10.png]]