상태 패턴(State Pattern)

  • 어떤 특정 상태에 따라 동작을 분기하기 위해서 조건문(if - else, switch)를 사용합니다.
  • 하지만 상태 패턴은 동작의 분기를 위해 조건문을 사용하지 않고, 객체를 캡슐화하여 독립된 동작으로 구분하는 패턴입니다.
  • 상태 패턴은 상태 표현 객체(Object for State)라고 부르기도 합니다.

상태에 따른 조건 분기: 기본적인 방법

  • 상태에 따라 조건 또는 동작을 분기하는 방법은 아래와 같은 방법을 많이 사용합니다.
enum class State
{
    A = 0,
    B,
    C,
    D 
};

// use if-else condition
if (state == State::A)
{
    // do something
}
else if (state == State::B)
{
    // ...
}
// ...

// use switch
switch (state)
{
case StateTest::A:
    // do something
    break;
case StateTest::B:
    break;
case StateTest::C:
    break;
case StateTest::D:
    break;
default:
    break;
}
  • 하지만 이와 같은 방법은 상태의 추가/삭제를 할 경우 조건문을 수정해야 하는 단점이 있습니다.
  • (참고) if-else는 비교 대상의 대/소를 비교합니다. 반면 switch는 값이 동일한 여부만 비교합니다. 상태를 구분할 경우 switch를 사용하는 것이 바람직합니다.
  • 상태 패턴을 통해 조건문을 사용하지 않고 상태를 객체로 분리하겠습니다.

상태 패턴 구조

  • 상태 패턴은 조건에 따른 상태 자체를 객체로 구현합니다.
  • 따라서 다양한 상태들을 공통적으로 처리하기 위한 인터페이스 클래스가 필요합니다.
  • 상태를 받고, 상태에 따른 처리를 진행할 상태 처리 객체가 필요합니다.
  • 위 내용을 정리하면 상태 패턴은 아래와 같은 구성요소가 필요합니다.
    • State
      • 상태의 인터페이스 클래스
    • ConcreteState
      • 각 상태에 따라 개별 동작들을 구현
    • Context
      • 상태를 전달받고, 이를 처리할 클래스

상태 패턴: 상태 인터페이스 클래스

  • 상태들의 공통적인 처리를 위해 인터페이스 클래스를 선언합니다.
// 인터페이스
class State
{
public:
    virtual void process() = 0;
};

상태 패턴: 상태 객체 구현

  • 상태에 따라 다르게 처리할 내용을 구현합니다.
  • State 클래스를 상속받아 process() 메서드에 작성합니다.
class StateOrder :
    public State
{
public:
    void process() override
    {
        std::cout << "[StateOrder] process" << std::endl;
    }
};

class StatePay :
    public State
{
public:
    void process() override
    {
        std::cout << "[StatePay] process" << std::endl;
    }
};

class StateOrdered :
    public State
{
public:
    void process() override
    {
        std::cout << "[StateOrdered] process" << std::endl;
    }
};

class StateFinish :
    public State
{
public:
    void process() override
    {
        std::cout << "[StateFinish] process" << std::endl;
    }
};

상태 패턴: 상태 관리 클래스

  • 상태들의 공통된 처리를 위해 State 클래스를 매개변수로 하는 상태 관리 클래스를 작성합니다.
  • 상태 관리 클래스는 담당하는 상태의 변경을 위해 setState() 메서드를 구현하여 런타임 상황에서 상태를 교체하도록 합니다.
class Context
{
public:
    Context(std::shared_ptr<State> state)
    {
        m_state = state;
    }

    void setState(std::shared_ptr<State> state)
    {
        if (m_state)
        {
            m_state.reset();
        }

        m_state = state;
    }

    void request()
    {
        m_state->process();
    }

private:
    std::shared_ptr<State> m_state;
};

상태 패턴: 실행 코드

  • 상태 패턴의 사용 방법을 알아봅니다.
  • 프로그램의 조건문을 제거하는 효과가 있습니다. 조건문은 코드를 읽는데 불편함을 초래하고 시스템 부하를 유발합니다.
  • 또한 상태 객체의 추가로 동작을 간단하게 확장할 수 있는 장점이 있습니다.
int main(const int argc, const char* argv[])
{
    std::shared_ptr<Context> context = std::make_shared<Context>(std::make_shared<StateOrder>());
    context->request();

    context->setState(std::make_shared<StatePay>());
    context->request();

    return 0;
}
  • 실행 결과
[StateOrder] process
[StatePay] process

상태 패턴: 추가 예제

  • 상태 패턴은 다음과 같은 형태로도 사용이 가능합니다.
  • 객체 프로퍼티에 상태값을 갖고 있다면 언제나 상태값을 확인하는 메서드를 구현해야 합니다.
class LightState
{
public:
    virtual void on() = 0;
    virtual void off() = 0;
    virtual bool state() = 0;
};

class LightLamp :
    public LightState
{
public:
    LightLamp()
    {
        m_state = false;
    }

    void on()
    {
        std::cout << "[LightLamp] on" << std::endl;
        m_state = true;
    }

    void off()
    {
        std::cout << "[LightLamp] off" << std::endl;
        m_state = false;
    }

    bool state()
    {
        std::cout << "[LightLamp] state(), " << m_state << std::endl;
        return m_state;
    }

private:
    bool m_state;
};