#고민리스트 #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]]