-
39장 - 태그 클래스보다는 클래스 계층을 사용하라Kotlin/Effective Kotlin 요약 2023. 3. 28. 00:01
태그 클래스
class ValueMatcher<T> private constructor( private val value: T? =null, private val matcher: Matcher ){ fun match(value: T?) = when(matcher){ Matcher.EQUAL -> value == this.value Matcher.LIST_EMPTY -> value is List<*> && value.isEmpty() } enum class Matcher{ EQUAL, LIST_EMPTY, } companion object{ fun <T> equal(value: T) = ValueMatcher<T>(value = value, matcher = Matcher.EQUAL) fun <T> emptyList() = ValueMatcher<T>(matcher = Matcher.LIST_EMPTY) } } fun main() { println(ValueMatcher.equal("text").match("text")) println(ValueMatcher.equal("text1").match("text")) println(ValueMatcher.emptyList<List<String>>().match(emptyList())) } //true //false //true
큰 프로젝트에서는 상수 모드를 가진 클래스를 볼 수 있습니다.
이런 클래스를 태그 클래스라 부릅니다.
태그 클래스의 문제점
서로 다른 책임을 한 클래그에 태그로 구분해서 넣는 것에서 시작합니다.
- 한 클래스에 여러 모드를 처리하기 위해 보일러플레이트가 추가됩니다
- 상황에 따라 인자가 사용되지 않을 수 있습니다.
태그 클래스보다 sealed 클래스
sealed class ValueMatcher<T>{ abstract fun match(value: T): Boolean class Equals<T>(private val value: T) : ValueMatcher<T>(){ override fun match(value: T): Boolean { return value == this.value } } class EmptyList<T>(): ValueMatcher<T>(){ override fun match(value: T): Boolean { return value is List<*> && value.isEmpty() } } } fun main() { val equalsMatcher = ValueMatcher.Equals<String>("text") println(equalsMatcher.match("text")) println(equalsMatcher.match("text1")) val listEmptyMatcher = ValueMatcher.EmptyList<List<String>>() println(listEmptyMatcher.match(emptyList())) } //결과 //true //false //true
sealed 클래스를 활용하면 책임이 분산되어 훨씬 깔끔하게 처리할 수 있습니다.
sealed 한정자
sealed 한정자는 외부 파일에서 서브 클래스를 만드는 행위 자체를 모두 제한합니다.
외부에서 서브 클래스를 만들 수 없으므로 타입이 추가되지 않을 것이라고 보장됩니다.
따라서 when을 사용할 때 else 분기를 따로 만들 필요가 없습니다.
태그 클래스와 상태 패턴의 차이
상태패턴은 서로 다른 상태를 나타내는 클래스 계층 구조를 만들 수 있습니다.
상태 패턴
interface DeliveryStatus { val name: String fun forward(delivery: Delivery): String fun backward(delivery: Delivery): String } // 배차대기 object Wait : DeliveryStatus { override val name = "배차대기" // 배차대기의 다음 상태는 배차완료 이므로 배차완료 상태로 변경한다. override fun forward(delivery: Delivery): String { delivery.status = Assign // 해당 배달을 매겨변수로 넘겨 받아 상태를 변경시킨다. return delivery.status.name } // 배차대기에서 뒤로 갈 상태가 없으므로 뒤로불가 리턴한다. override fun backward(delivery: Delivery) = "뒤로불가" } // 배차완료 object Assign : DeliveryStatus { override val name = "배차완료" override fun forward(delivery: Delivery): String { delivery.status = Pickup return delivery.status.name } override fun backward(delivery: Delivery): String { delivery.status = Wait return delivery.status.name } } // 픽업완료 object Pickup : DeliveryStatus { override val name = "픽업완료" override fun forward(delivery: Delivery): String { delivery.status = Complete return delivery.status.name } override fun backward(delivery: Delivery): String { delivery.status = Assign return delivery.status.name } } // 전달완료 object Complete : DeliveryStatus { override val name = "전달완료" // 전달완료는 다음상태가 없기 때문에 진행불가 override fun forward(delivery: Delivery) = "진행불가" override fun backward(delivery: Delivery): String { delivery.status = Pickup return delivery.status.name } } class Delivery { var status: DeliveryStatus = Wait fun forwardStatus() = status.forward(this) fun backwardStatus() = status.backward(this) } fun main() { val delivery = Delivery() println(delivery.status.name) delivery.forwardStatus() // 배차대기 => 배차완료 변경 println(delivery.status.name) delivery.forwardStatus() // 배차완료 => 픽업완료 변경 println(delivery.status.name) delivery.backwardStatus() // 픽업완료 => 배차완료 println(delivery.status.name) }
배달에 대한 상태를 대기 -> 배차완료 -> 픽업완료 -> 배차완료로 fowardStatus, backwardStatus를 통해 이동이 가능해집니다.
즉, 상태에 대한 변경이 자유롭게 일어나게 됩니다.
모바일 UI의 Sleaed Class With State Pattern
안드로이드 진영에서 UI를 Sealed Class와 State Pattern을 활용하는 모습은 다음글을 참조하시면 좋을 것 같습니다.
https://yoon-dailylife.tistory.com/82
참고자료
https://kimchanjung.github.io/design-pattern/2020/05/26/state-pattern/
'Kotlin > Effective Kotlin 요약' 카테고리의 다른 글
41장 - hashCode의 규약을 지켜라 (0) 2023.04.04 40장 - equals 규약을 지켜라 (0) 2023.03.29 38장 - 연산 또는 액션을 전달할 때는 인터페이스 대신 함수 타입을 사용하라 (0) 2023.03.27 37장 - 데이터 집합 표현에 data 한정자를 사용하라 (0) 2023.03.26 36장 - 상속보다는 컴포지션을 사용하라 (0) 2023.03.25