Topic = 상태 패턴의 개념에 대해서 학습해보고 Kotlin에서 적용 예시를 만들어보기.
🔑 Purpose
- 객체의 상태를 캡슐화한 클래스들로 분리하여, 복잡한 조건문을 사용하지 않고, 위임을 통해 객체의 상태에 따라 다른 행동을 할 수 있도록 한다.
Why
위 예시 사진 이미지와 같이 TodoList의 각각의 할 일들의 개별 속성 ( 마감기한, 선택목록, 체크박스, 텍스트, 백분율 등 ) 의 표기는 해당 객체의 속성이 날짜면 날짜 데이터만을 표기하고, 백분율은 숫자만을 표기하며 %로 표기하는 등 각각의 상태에 따라 다른 표기 방식을 갖게 된다.
위 상황을 상태 패턴을 적용하지 않은 코드로 일부분을 작성해보면 다음과 같이 작성할 수 있다.
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger { }
class Task(private var categoryType: String = CATEGORY_TEXT) {
companion object {
const val CATEGORY_TEXT = "text"
const val CATEGORY_DATE = "date"
const val CATEGORY_NUMBER = "number"
}
fun setCategoryType(categoryType: String) {
this.categoryType = categoryType
}
fun write(content: String) {
when(categoryType) {
CATEGORY_TEXT -> {
logger.info { "TEXT = $content" }
}
CATEGORY_DATE -> {
// 날짜 선택 로직
logger.info { "DATE = 날짜 " }
}
CATEGORY_NUMBER -> {
val result = content.replace(Regex("[^0-9]"), "")
logger.info { "NUMBER = $result" }
}
}
}
}
카테고리 종류가 10개가 넘어가기만 해도 when 조건문 안에 10개 이상의 조건이 생기게 되는 것이다. 때문에 조건문 코드가 비대해지는 문제가 발생하고, 객체 내부에서 상태 관리와 비즈니스 로직 코드가 함께 있으므로 SRP 를 어기게 되는 문제도 발생한다.
이를 해결하기 위해 상태의 행동에 관련된 로직을 캡슐화하여 객체에서 분리해서 사용하는 것이 바로 상태 패턴이다.
위 예제를 상태 패턴의 구조를 학습하면서 상태 패턴이 적용된 코드로 바꾸어 보자.
상태 패턴의 구조
[ 상태 패턴 - UML 다이어그램 ]
+------------------+ +----------------+
| Context | | <<State>> |
+------------------+ +----------------+
| - state: State |<>----->| +handle():Unit |
+------------------+ +----------------+
| +setState(State) | | |
| +request() | | |
+------------------+ +----------------+
/\
||
+---------------------+---------------------+
| |
+-------------------------+ +-------------------------+
| ConcreteStateA | | ConcreteStateB |
+-------------------------+ +-------------------------+
| +handle():Unit | | +handle():Unit |
+-------------------------+ +-------------------------+
[ Context ]
- 현재 상태를 추적하여 ( 현재 상태를 유지하고, 상태 변경 시 자신의 상태를 업데이트하는 것 ) 상태에 따라 행동 동작을 위임하는 객체이다.
class StatePatternTask(private var categoryType: Category = Text()) {
fun setCategoryType(categoryType: Category) {
this.categoryType = categoryType
}
fun write(content: String) {
categoryType.write(content)
}
}
[ State Interface ]
- 상태의 행동을 정의하는 인터페이스이다.
interface Category {
fun write(content: String)
}
[ Concrete State ]
- 상태 인터페이스를 구현하는 구체적인 상태 클래스로, 각각의 상태의 행동을 정의한다.
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger { }
class Date : Category {
override fun write(content: String) {
// 날짜 선택 로직
logger.info { "DATE = 날짜 " }
}
}
class Number : Category{
override fun write(content: String) {
val result = content.replace(Regex("[^0-9]"), "")
logger.info { "Number.write = $result" }
}
}
class Text: Category {
override fun write(content: String) {
logger.info { "Text = $content" }
}
}
상태 패턴 장점, 단점 정리
[ 상태 패턴의 장점 ]
- 복잡한 조건문 제거
- 객체의 상태에 따라 행동을 다르게 해야 하는 경우 if, when 구문을 사용할 필요 없이, 각 상태를 객체로 분리하여 조건문 없이 상태에 따른 로직을 구현할 수 있다.
- 유연한 상태 변경 - SRP, OCP
- task객체 내부에서 상태에 관한 로직을 관리하지 않고, 상태 클래스들을 독립적으로 관리하므로 새로운 상태가 추가되거나, 기존 상태가 변경되어도 task 객체를 건드리지 않고도 기능 확장, 수정이 가능하다.
[ 상태 패턴의 단점 ]
- 복잡도 증가
- 상태 클래스가 많아질수록 클래스 수가 증가하므로 복잡도가 높아질 수도 있다.
- ➡️상태 클래스가 별도의 상태를 갖지 않아 클래스 생성이 필요하지 않고, 상태가 정형화 되어 있고, 상태 전이가 복잡하지 않은 경우에 Enum class를 사용하여 상태를 표현하여 복잡도를 낮추는 방법도 있다.
[ 다음에 학습할 내용 ]
- UML 다이어그램 정리
- ENUM
[Reference]
'웹개발 - Back 관련 > Architecture' 카테고리의 다른 글
[ GOF ] 브릿지 패턴 - Bridge Pattern 개념 (0) | 2024.10.29 |
---|---|
[ GOF ] 파사드 패턴 - Facade Pattern with Kotlin (0) | 2024.10.15 |
[ GOF ] 싱글톤 패턴, object - Singleton Pattern with Kotlin (6) | 2024.10.08 |
[ GOF ] 팩토리 매서드 패턴 - Factory Method Pattern with Kotlin (0) | 2024.09.11 |
[ GOF ] 전략 패턴 - Strategy Pattern with Koltin (0) | 2024.09.10 |