상태(state) 패턴 한번 써 볼까?

일이 일이다 보니 상태(state)를 다룰 일이 아주 많습니다. 상태로 시작해 상태로 끝난다고 해도 과언이 아닐 정도지만, 패턴에 익숙하지 않아 그다지 쓰지 않다 보니 보통 그렇듯 간편하게(?) 상태 값으로 처리합니다. 이미 책과 인터넷에서 많이 찾아 볼 수 있는 내용이긴 하겠지만 상태 패턴을 한번 써 보기로 맘 먹은지라 기억도 되살릴겸 간단히 정리해 봅니다.

IMM Object State

IMM 객체 상태 도해인데 이게 뭔지는 중요하지 않으니 넘어갑니다. 중요한 것은 이 객체에 크게 IN SERVICE, NOT IN SERVICE로 두 가지 상태가 있고 NOT IN SERVICE에는 하위 상태인 EXTERNAL SELECT, INTERNAL SELECT 상태가 있다는 점입니다. 화살표는 상태 전이 경로를 나타냅니다. 정리하면 다음과 같습니다.

 From To
IN SERVICE EXTERNAL SELECT
IN SERVICE INTERNAL SELECT
EXTERNAL SELECT IN SERVICE
INTERNAL SELECT IN SERVICE

상태 패턴에서는 흔히 상태 값으로 나타내는 상태 하나하나를 모두 객체로 정의하며 상태 전이를 일으키는 방아쇠(trigger)를 메소드로  정의합니다. 그럼 실제로 구현하기 전에 일반적인 상태 패턴을 보겠습니다.

State Diagram

그림은 Head First Design Patterns에서 가져왔습니다. 간단히 설명하면 Context에서는 인터페이스인 State만 사용해 handle()을 호출하는데 실제 호출하는 handle()ConcreteStateA::handle() 또는 ConcreteStateB::handle()이 됩니다. 이는 당시 Context 상태 전이에 따라 처리합니다. 그럼 다시 처음 상태 도해를 바탕으로 클래스 도해를 만들어 봅니다.

IMM Class Diagram

이제 실제 코드 예를 보겠습니다. 먼저 ImmTransitionState 클래스입니다.

#ifndef IMM_TRANSITION_STATE_H_
#define IMM_TRANSITION_STATE_H_

class ImmTransitionState {
public:
  virtual void toInService() = 0;
  virtual void toNoInServiceExternal() = 0;
  virtual void toNoInServiceInternal() = 0;
};

#endif

ImmTransitionInService 클래스입니다.

#ifndef IMM_TRANSITION_INSERVICE_H_
#define IMM_TRANSITION_INSERVICE_H_

#include "ImmTransitionState.h"

class ImmObject;

class ImmTransitionInService : public ImmTransitionState {
public:
  ImmTransitionInService(ImmObject* immObj)
      : immObject_(immObj) {};
  ~ImmTransitionInService() {};

  virtual void toInService();
  virtual void toNoInServiceExternal();
  virtual void toNoInServiceInternal();

private:
  ImmObject* const immObject_;
};

#endif

 

#include <iostream>
#include "ImmTransitionInService.h"
#include "ImmObject.h"

void ImmTransitionInService::toInService() {
  std::cout << "Already here!" << std::endl;
  std::cout << "InService State" << std::endl << std::endl;
  return;
}

void ImmTransitionInService::toNoInServiceExternal() {
  std::cout << "ImmTransitionInService::toNoInServiceExternal()" << std::endl;
  immObject_->transitionState(immObject_->getNoInServiceExteranlState());
  std::cout << "External State" << std::endl << std::endl;
  return;
}

void ImmTransitionInService::toNoInServiceInternal() {
  std::cout << "ImmTransitionInService::toNoInServiceInternal()" << std::endl;
  immObject_->transitionState(immObject_->getNoInServiceInteranlState());
  std::cout << "Internal State" << std::endl << std::endl;
  return;
}

ImmTransitionNoInServiceExternal 클래스입니다.

#ifndef IMM_TRANSITION_NOINSERVICE_EXTERNAL_H_
#define IMM_TRANSITION_NOINSERVICE_EXTERNAL_H_

#include "ImmTransitionState.h"

class ImmObject;

class ImmTransitionNoInServiceExteranl : public ImmTransitionState {
public:
  ImmTransitionNoInServiceExteranl(ImmObject* immObj)
      : immObject_(immObj) {};
  ~ImmTransitionNoInServiceExteranl() {};

  virtual void toInService();
  virtual void toNoInServiceExternal();
  virtual void toNoInServiceInternal();

private:
  ImmObject* const immObject_;
};

#endif

 

#include <iostream>
#include "ImmTransitionNoInServiceExternal.h"
#include "ImmObject.h"

void ImmTransitionNoInServiceExteranl::toInService() {
  std::cout << "ImmTransitionNoInServiceExteranl::toInService()" << std::endl;
  immObject_->transitionState(immObject_->getInServiceState());
  std::cout << "InService State" << std::endl << std::endl;
  return;
}

void ImmTransitionNoInServiceExteranl::toNoInServiceExternal() {
  std::cout << "Already here!" << std::endl;
  std::cout << "External State" << std::endl << std::endl;
  return;
}

void ImmTransitionNoInServiceExteranl::toNoInServiceInternal() {
  std::cout << "ImmTransitionNoInServiceExteranl::toNoInServiceInternal()"
      << std::endl;
  std::cout << "Invalid Transition!" << std::endl;
  std::cout << "External State" << std::endl << std::endl;
  return;
}

ImmTransitionNoInServiceInteranl 클래스입니다.

#ifndef IMM_TRANSITION_NOINSERVICE_INTERNAL_H_
#define IMM_TRANSITION_NOINSERVICE_INTERNAL_H_

#include "ImmTransitionState.h"

class ImmObject;

class ImmTransitionNoInServiceInteranl : public ImmTransitionState {
public:
  ImmTransitionNoInServiceInteranl(ImmObject* immObj)
      : immObject_(immObj) {};
  ~ImmTransitionNoInServiceInteranl() {};

  virtual void toInService();
  virtual void toNoInServiceExternal();
  virtual void toNoInServiceInternal();

private:
  ImmObject* const immObject_;
};

#endif

 

#include "stdafx.h"
#include <iostream>
#include "ImmTransitionNoInServiceInternal.h"
#include "ImmObject.h"

void ImmTransitionNoInServiceInteranl::toInService() {
  std::cout << "ImmTransitionNoInServiceInteranl::toInService()" << std::endl;
  immObject_->transitionState(immObject_->getInServiceState());
  std::cout << "InService State" << std::endl << std::endl;
  return;
}

void ImmTransitionNoInServiceInteranl::toNoInServiceExternal() {
  std::cout << "ImmTransitionNoInServiceInteranl::toNoInServiceExternal()"
      << std::endl;
  std::cout << "Invalid Transition!" << std::endl;
  std::cout << "External State" << std::endl << std::endl;
  return;
}

void ImmTransitionNoInServiceInteranl::toNoInServiceInternal() {
  std::cout << "Already here!" << std::endl;
  std::cout << "Internal State" << std::endl << std::endl;
  return;
}

ImmObject 클래스입니다.

#ifndef IMM_OBJECT_H_
#define IMM_OBJECT_H_

#include <memory>
#include "ImmTransitionState.h"

class ImmObject {
public:
  ImmObject();
  ~ImmObject() {};

  void toInService();
  void toNoInServiceExternal();
  void toNoInServiceInternal();

  void transitionState(std::shared_ptr<ImmTransitionState> state) {
    state_ = state;
  }

  std::shared_ptr<ImmTransitionState> getInServiceState() {
    return inService_;
  }

  std::shared_ptr<ImmTransitionState> getNoInServiceExteranlState() {
    return exteranl_;
  }

  std::shared_ptr<ImmTransitionState> getNoInServiceInteranlState() {
    return interanl_;
  }

private:
  std::shared_ptr<ImmTransitionState> inService_;
  std::shared_ptr<ImmTransitionState> exteranl_;
  std::shared_ptr<ImmTransitionState> interanl_;
  std::shared_ptr<ImmTransitionState> state_;
};

#endif

 

#include "ImmObject.h"
#include "ImmTransitionInService.h"
#include "ImmTransitionNoInServiceExternal.h"
#include "ImmTransitionNoInServiceInternal.h"

ImmObject::ImmObject() {
  inService_.reset(new ImmTransitionInService(this));
  exteranl_.reset(new ImmTransitionNoInServiceExteranl(this));
  interanl_.reset(new ImmTransitionNoInServiceInteranl(this));

  state_ = interanl_;
}

void ImmObject::toInService() {
  state_->toInService();
}

void ImmObject::toNoInServiceExternal() {
  state_->toNoInServiceExternal();
}

void ImmObject::toNoInServiceInternal() {
  state_->toNoInServiceInternal();
}

마지막으로 시험 코드입니다.

#include <memory>
#include "ImmObject.h"

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<ImmObject> immObj(new ImmObject);

  immObj->toInService();
  immObj->toNoInServiceExternal();
  immObj->toNoInServiceInternal();
  immObj->toNoInServiceInternal();
  immObj->toNoInServiceExternal();
  immObj->toInService();
  immObj->toInService();
  immObj->toNoInServiceInternal();
  immObj->toNoInServiceExternal();
  immObj->toInService();

  return 0;
}

You may also like...

  • selex

    글 잘 읽었습니다.
    만약 새로운 상태를 추가한다면 인터페이스를 구현한 새로운 상태를 추가하고
    이미 구현된 클래스를 수정해야 할 듯 한데
    일이 많아 질 듯 하네요.

    그리고 ImmObject::transitionState 함수가 public이어서
    직접 호출할 수 있네요. 이 문제를 해결할 수 있나요?

    • 상태 패턴 구현에는 몇 가지 방법이 있습니다. 위 예에서는 상태를 조건에 따라 스스로 바꾸므로 상태를 관리할 필요가 없는 장점이 있으나 각 상태 클래스에서 서로 참조해야 하므로 상태 추가로 인해 기존 코드를 변경해야 하는 단점이 있습니다. 물론 ImmObject::transitionState()를 public으로 한 이유 역시 서로 참조를 해야 하므로 생기는 단점입니다.

      이를 피하려면 단순히 인터페이스만을 이용하고 상태를 스스로 바꾸는 것이 아니라 클라이언트에서 각 상태별 객체를 직접 설정해 상태를 변경해 주는 방법이 있습니다. 이때도 ImmObject::transitionState()와 같은 상태 변경 메소드는 public으로 선언해야겠지만 명시적으로 사용하기 위한 것이니 위와는 상황이 좀 다르죠.

  • Kim

    ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ>
    이건 어떻게 쓰나요?? visual paradigm 에서 쓸려고해도 없더군요 ㅜㅜ

    • 위 그림 중 두 번째 클래스도에 있는 ImmObject와 ImmTransitionState 사이 관계를 말씀하시는 거라면 연관 관계를 단 방향으로 설정하면 되는 걸로 기억하는데 보면서 얘기할 수 없는 상황이라 양해 바랍니다.