상태 패턴(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;
};