State 패턴
하나의 객체가 다양한 상태를 가질 때가 있습니다.
또한, 상태가 변화함에 따라 행위가 달라질 수 있습니다.
만약 어떤 상태에 대한 `행위를 클래스별로 구현`해주어야 한다면 상당한 `보일러플레이트 코드`가 발생할 것입니다.
이는 곧 프로젝트의 유지보수성을 떨어뜨리는 것과 동일합니다.
이러한 상황을 쉽게 관리하기 위해 State 패턴을 사용할 수 있습니다.
State 패턴은 `상태에 따른 비즈니스 로직`, `상태 전이`를 상태 클래스 내에서 관리합니다.
이러한 상태를 가지고 있는 객체를 `Context`라고 부릅니다.
State 패턴을 적용하면 Context는 각 상태에 따라 어떤 기능을 수행할지 고려하지 않아도 됩니다.
State 패턴을 적용하지 않은 사례
// 자동차는 아래와 같은 상태를 지닌다.
// 1. 고장 (고장난 상태)
// 2. 노말 (고장나지 않은 상태)
// 3. 파워 (파워 모드 상태)
data class Car(
val position: Int,
val state: String,
) {
fun move(): Car {
when(state) {
"고장" -> return copy() // 움직이지 못한다.
"노말" -> return copy(position = position + 1) // 한 칸 이동한다.
"파워" -> return copy(position = position + 2) // 두 칸 이동한다.
}
throw IllegalStateException("올바른 상태가 아닙니다.")
}
fun drift(): Car {
when(state) {
"고장" -> return copy() // 움직이지 못한다.
"노말" -> return copy(position = position + 5) // 정상 모드에서 드리프트하면 5칸 이동할 수 있다.
"파워" -> return copy(state = "고장") // 파워 모드에서 드리프트하면 고장난다.
}
throw IllegalStateException("올바른 상태가 아닙니다.")
}
fun moveBack(): Car {
when(state) {
"고장" -> return copy() // 움직이지 못한다.
"노말" -> return copy(position = position - 1) // 뒤로 한 칸 이동한다.
"파워" -> return copy(position = position - 2) // 뒤로 두 칸 이동한다.
}
throw IllegalStateException("올바른 상태가 아닙니다.")
}
}
위 코드는 상태 패턴을 적용하지 않은 상황입니다.
자동차는 `고장` `노말 모드` `파워 모드` 를 가지고 있습니다.
자동차의 멤버 메서드를 호출하면, 조건문을 통해 각 상태에 따른 기능을 수행합니다.
위 코드에는 문제점이 있습니다.
자동차의 로직이 추가됨에 따라 매번 자동차의 상태를 조건문을 통해 확인해주어야 한다는 것입니다.
이는 반복적이고 비슷한 형태의 코드를 초례합니다.
또한, 상태가 추가되었을 때 자칫 조건문을 하나라도 추가해주지 않으면 Exception이 발생하여 예상치 못한 결과가 발생합니다.
상태 코드를 적용한다면?
interface CarState {
fun move(car: Car): Car
fun drift(car: Car): Car
fun moveBack(car: Car): Car
}
문제를 파악했으니, State 패턴을 적용하여 위 문제를 해결해보겠습니다.
자동차의 상태에 따라 위의 3가지 행위를 수행할 수 있습니다.
CarState 인터페이스를 구현하는 상태 클래스를 구현해보겠습니다.
고장난 상태
import Car
import CarState
class Broken : CarState {
// 움직이지 못한다.
override fun move(car: Car): Car = car.copy()
// 드리프트 하지 못한다.
override fun drift(car: Car): Car = car.copy()
// 후진하지 못한다
override fun moveBack(car: Car): Car = car.copy()
}
노말 모드 상태
import Car
import CarState
class Normal : CarState {
override fun move(car: Car): Car =
car.copy(position = car.position + 1)
override fun drift(car: Car): Car =
car.copy(position = car.position + 5)
override fun moveBack(car: Car): Car =
car.copy(position = car.position - 1)
}
파워 모드 상태
import Car
import CarState
class Power : CarState {
override fun move(car: Car): Car =
car.copy(position = car.position + 2)
override fun drift(car: Car): Car =
car.copy(state = Broken())
override fun moveBack(car: Car): Car =
car.copy(position = car.position - 2)
}
자동차 클래스
data class Car(
val position: Int,
val state: CarState,
) {
fun move(): Car = state.move(this)
fun drift(): Car = state.drift(this)
fun moveBack(): Car = state.moveBack(this)
}