[Design Pattern] State Pattern
state pattern에 대하여 알아보고, java와 kotlin 예제를 봐보도록 하겠습니다.
State Pattern이란??
State Pattern은 일련의 규칙에 따라 객체의 상태(State)를 변화시켜, 객체가 할 수 있는 행위를 바꾸는 패턴입니다.
상태에 따라 행동이 변화하는 객체엔 모두 적용할 수 있습니다.
특징
State Pattern은 객체의 특정 상태를 클래스로 선언하고, 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의합니다.
그리고 이러한 각 상태 클래스들을 인터페이스로 캡슐화하여, 클라이언트에서 인터페이스를 호출하는 방식을 말합니다.
State Pattern with Java
public interface PowerState {
public void powerPush();
}
public class On implements PowerState{
public void powerPush(){
System.out.println("power on");
}
}
public class Off implements PowerState {
public void powerPush(){
System.out.println("power off");
}
}
public class Saving implements PowerState {
public void powerPush(){
System.out.println("power saving");
}
}
public class Laptop {
private PowerState powerState;
public Laptop(){
this.powerState = new Off();
}
public void setPowerState(PowerState powerState){
this.powerState = powerState;
}
public void powerPush(){
powerState.powerPush();
}
}
public class Client {
public static void main(String args[]){
Laptop laptop = new Laptop();
On on = new On();
Off off = new Off();
Saving saving = new Saving();
laptop.powerPush();
laptop.setPowerState(on);
laptop.powerPush();
laptop.setPowerState(saving);
laptop.powerPush();
laptop.setPowerState(off);
laptop.powerPush();
laptop.setPowerState(on);
laptop.powerPush();
}
}
State Pattern with kotlin
ex) Login Authorization
sealed class AuthorizationState{
object Unauthorized : AuthorizationState()
class Authorized(val userName: String) : AuthorizationState()
}
class AuthorizationPresenter {
private var state: AuthorizationState = AuthorizationState.Unauthorized
val isAuthorized: Boolean
get() = when (state) {
is AuthorizationState.Authorized -> true
is AuthorizationState.Unauthorized -> false
}
val userName: String
get() {
return when (val state = this.state) { //val enables smart casting of state
is AuthorizationState.Authorized -> state.userName
is AuthorizationState.Unauthorized -> "Unknown"
}
}
fun loginUser(userName: String) {
state = AuthorizationState.Authorized(userName)
}
fun logoutUser() {
state = AuthorizationState.Unauthorized
}
override fun toString() = "User '$userName' is logged in: $isAuthorized"
}
class StateTest {
@Test
fun `State`() {
val authorizationPresenter = AuthorizationPresenter()
authorizationPresenter.loginUser("admin")
println(authorizationPresenter)
assertThat(authorizationPresenter.isAuthorized, CoreMatchers.equalTo(true))
assertThat(authorizationPresenter.userName, CoreMatchers.equalTo("admin"))
authorizationPresenter.logoutUser()
println(authorizationPresenter)
assertThat(authorizationPresenter.isAuthorized, CoreMatchers.equalTo(false))
assertThat(authorizationPresenter.userName, CoreMatchers.equalTo("Unknown"))
}
}
state를 sealed class로 묶어 관리하는 방법입니다.
sealed 클래스를 사용하면 좋은 점은 sealed 클래스 안의 클래스들을 모두 알고 있기 때문에
when 문으로 else 없이도 클래스들을 관리할 수 있습니다.
ex2)
interface State {
fun handleRequest(): List<String>
}
class ShowProducts(private val vendingMachine: VendingMachine, private val request: List<String>) : State {
override fun handleRequest(): List<String> {
val result = request + "Show Products"
vendingMachine.state = SelectProduct(vendingMachine, result)
return result
}
}
class SelectProduct(private val vendingMachine: VendingMachine, private val request: List<String>) : State {
override fun handleRequest(): List<String> {
val result = request + "Select Product"
vendingMachine.state = DepositMoney(vendingMachine, result)
return result
}
}
class DepositMoney(private val vendingMachine: VendingMachine, private val request: List<String>) : State {
override fun handleRequest(): List<String> {
val result = request + "Deposit Money"
vendingMachine.state = DeliverProduct(vendingMachine, result)
return result
}
}
class DeliverProduct(private val vendingMachine: VendingMachine, private val request: List<String>) : State {
override fun handleRequest(): List<String> {
val result = request + "Deliver Product"
vendingMachine.state = ShowProducts(vendingMachine, result)
return result
}
}
class VendingMachine {
var state: State = ShowProducts(this, listOf())
fun proceed(): List<String> = state.handleRequest()
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StateTest {
@Test
@DisplayName("Given a vending machine. When proceed with the steps. Then handle the request in each step as per current vending machine state")
fun givenVendingMachine_whenProceedWithSteps_thenHandleRequestInEachStepAsPerCurrentVendingMachineState() {
// Given
val vendingMachine = VendingMachine()
// When
vendingMachine.proceed() // execute show products step and set the next vending machine state
vendingMachine.proceed() // execute select product step and set the next vending machine state
vendingMachine.proceed() // execute deposit money step and set the next vending machine state
val result =
vendingMachine.proceed() // execute deliver product step and set the initial vending machine state
// Then
val expectedResult =
listOf("Show Products", "Select Product", "Deposit Money", "Deliver Product")
Assert.assertThat(result, CoreMatchers.equalTo(expectedResult))
}
}
vendingMachine의 상태를 proceed 메서드를 호출할 때마다 변경해줍니다( 책임 연쇄 패턴이기도 합니다)
State 인터페이스를 구현한 클래스들로 상태를 관리합니다.
잠깐?? 전략 패턴과 비슷하다고요?
스테이트 구현 과정을 보면, 전략 패턴과 상당히 유사합니다.
거의 동일한 구조입니다.
굳이 사용을 구분하자면,
전략 패턴은 상속을 대체하려는 목적으로, 스테이트 패턴은 코드 내의 조건문들을 대체하려는 목적으로 사용됩니다.
참고 - https://github.com/KWSStudy/Refactoring/issues/2
정리
State Pattern은 일련의 규칙에 따라 객체의 상태(State)를 변화시켜, 객체가 할 수 있는 행위를 바꾸는 패턴입니다.
상태에 따라 행동이 변화하는 객체엔 모두 적용할 수 있습니다.
샘플 코드 보러 가기
https://github.com/qjatjr1108/DesignPattern
'DesignPattern' 카테고리의 다른 글
[Design Pattern] Visitor Pattern (0) | 2019.10.21 |
---|---|
[Design Pattern] Chain of Responsibility Pattern (0) | 2019.10.21 |
[Design Pattern] Command Pattern (0) | 2019.10.21 |
[Design Pattern] Interpreter Pattern (0) | 2019.10.21 |
[Design Pattern] Iterator Pattern (0) | 2019.10.18 |
댓글