본문 바로가기
DesignPattern

[Design Pattern] State Pattern

by 봄석 2019. 10. 21.

[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

 

Strategy패턴과 State패턴 · Issue #2 · KWSStudy/Refactoring

안녕하세요 조아라입니다. 저번 스터디에서 리펙토링에 대해 좋은 내용을 많이 들을 수 있어 유익한 스터디 같습니다 ! : ) 어쩌면 리펙토링과 관련이 없을 수도 있는 내용일 수도 있는데 계속 의문점이 들어 여쭤봅니다. 저번 스터디 에서 Switch문의 내용을 패턴을 적용해서 클래스로 만들 때 적용한 패턴이 State패턴이었는데요~ 제가 아는 전략 패턴과...

github.com

 

 

 

정리

State Pattern은 일련의 규칙에 따라 객체의 상태(State)를 변화시켜, 객체가 할 수 있는 행위를 바꾸는 패턴입니다.
상태에 따라 행동이 변화하는 객체엔 모두 적용할 수 있습니다.

 

샘플 코드 보러 가기

https://github.com/qjatjr1108/DesignPattern

 

qjatjr1108/DesignPattern

DesignPattern Sample. Contribute to qjatjr1108/DesignPattern development by creating an account on GitHub.

github.com

 

댓글