블랙잭 미션을 하던 중 한 가지 문제가 있었다.
블랙잭 결과를 생성하려면 딜러와 플레이어가 게임이 끝난 상태가 되어있어야 한다는 조건을 BlackjackResult 클래스 코드에 명확하게 표현하고 싶었는데, 좋은 방법을 찾지 못했다. 그래서 어쩔 수 없이 딜러와 플레이어의 카드 개수가 2장 이상이고 딜러의 점수가 16점 이하가 아닌지 체크해서 만약 이 조건을 만족하지 않는다면 딜러나 플레이어들 중 게임이 끝난 상태가 아니라고 판단하고 블랙잭 결과를 생성할 수 없도록 구현했다.
그러던 중 수업시간에 상태 패턴이라는 디자인 패턴을 배우고 이 패턴을 블랙잭 미션에 적용하며 공부해보았다.
상태 패턴은 일련의 규칙에 따라 객체의 상태를 변화시켜, 객체가 할 수 있는 행위를 바꾸는 패턴을 말한다. 어떤 상태로 변하게 할 지 결정하는 주체는 상태를 소유한 객체가 아니라 상태 그 자체이다. 상태를 소유한 객체는 상태에게 같은 메세지를 보내면 상태는 스스로 상태에 맞는 행위를 한다.
블랙잭 미션에서 상태 패턴을 적용한 예제이다.
상태를 소유한 객체인 Participant에서는 State 인터페이스에 정의된 메서드만 사용해도 비즈니스 로직에 맞는 상태로 변경되고 상태에 맞는 메서드가 실행된다. 상태의 구체 클래스에 의존하는 게 아니라 인터페이스에 의존하고 있으니 DIP(의존성 역전 원칙)을 지킬 수 있게 된다.
if/else/when
같은 분기문을 효과적으로 제거할 수 있다.class WaterMachine {
private var state: WaterMachineState = WaterMachineState.NORMAL
fun clickStateButton() {
state = state.click()
}
fun getWater() {
println("물이 나오는 중")
}
}
enum class WaterMachineState {
NORMAL {
override fun click(): WaterMachineState {
return COLD
}
},
COLD {
override fun click(): WaterMachineState {
return HOT
}
},
HOT {
override fun click(): WaterMachineState {
return NORMAL
}
}, ;
abstract fun click(): WaterMachineState
}
정수기의 버튼을 누르면 상태가 변경된다. 그리고 물을 나오게 하면 상태에 맞는 물이 나오게 된다. 이처럼 상태가 가져야할 인스턴스 변수가 없거나, 인스턴스 변수가 고정되어 있다면 enum class로 선언할 수도 있다.