read

프로그램의 상태에 따라 처리할 게 다르다면 어떻게 처리해야 될까요? 제일 먼저 생각나는 방법은 분기문이네요. if, else 를 써서 각 state 에 따라 수행할 코드들을 쓰는 겁니다. 그런데 이렇게 하게 되면 시간이 지나면서 분기문이 소스코드 곳곳에 생기게 됩니다.

하나 하나 살펴보도록 하지요. 먼저 Context.Request() 라는 함수에서 콜되는 부분을 따라가면 아래처럼 분기문이 있었을 겁니다.

class Context
{
STATECODE State;
Request()
{
Handle();
}

Handle()
{
if (State == Initiating)
{
// Do some initialization works
}
else if (State == Running)
{
// Do some works for Running mode
}
else if (State == Idling)
{
// Do some works for Idling mode
}
}
}

분기문을 쓰는 함수가 Request() 하나 였다면 크게 문제가 없을 겁니다만, 보통은 코딩하다 보면 아래처럼 여러개가 생기곤 합니다.

class Context
{
STATECODE State;
Request()
{
Handle();
}

Handle()
{
if (State == Initiating)
{
// Do some initialization works
}
else if (State == Running)
{
// Do some works for Running mode
}
else if (State == Idling)
{
// Do some works for Idling mode
}
}

GetValue()
{
if (State == Initiating)
return 0;
else if (State == Running)
return 1;
else if (State == Idling)
return 2;
}
}

좀 단순하게 표현한 것이지만, 위의 GetValue() 함수 처럼 if-else 구문을 포함하는 게 늘어날 수 있다는 의미입니다. 이걸 없애기 위해서 State pattern 을 도입합니다. State pattern 은 이 if-else 구문을 상속을 통해 애초에 없애는 패턴입니다.State_Design_Pattern_UML_Class_Diagram

State pattern 을 적용해서 Context 클래스를 바꿔보면 다음처럼 됩니다.

class Context
{
State
* pState;
Request()
{
pState
->Handle();
}

GetValue()
{
return pState->GetValue();
}
}

분기문이 보이지 않네요. State 클래스들도 추가해 보지요.

class State
{
Handle();
GetValue();
};

class InitiatingState : State
{
Handle()
{
// Do some initiating works
}
GetValue()
{
return 0; }
};
class RunningState : State
{
Handle()
{
// Do some works for running mode
}
GetValue()
{
return 1; }
};
class IdlingState : State
{
Handle()
{
// Do some works for Idling mode
}
GetValue()
{
return 2; }
};

이로써 if-else 구문은 없어지고 if else 의 각 분기에 해당하는 코드들은 State 클래스를 상속받아 구현한 ConcreteState Class 들 즉, InitiatingState, RunningState, IdlingState 로 나눠졌습니다. 물론 여기서 Context 에 있는 State 포인터 값을 바꿔주는 코드가 따로 필요 합니다.

void main()
{
Context ct;
ct.pState
= new InitiatingState();
ct.Request();

ct.pState = new RunningState();
print ct.GetValue();

ct.pState = new IdlingState();
ct.Request();
}

Context 를 쓰는 클라이언트 코드는 위처럼 되겠지요. 이렇게 하면 새로운 State 가 추가되거나 기존의 State 에 따른 코드가 바뀌어야 할 때, 소스코드 전체에 퍼져 있는 if-else 를 찾아 바꿔줘야 하는 수고를 덜 수 있습니다. 해당 ConcreteState 코드만 바꿔주거나 추가하면 충분합니다.

하지만, 위 코드도 약간 개선할 점들이 있습니다. 보시다시피 State 가 바뀔때마다 새로운 ConcreteState 인스턴스를 만들어 할당해줘야 되기 때문에, 불필요한 메모리 할당이 자주 일어날 수 있습니다. 이럴 경우 Singleton pattern 이나 Flyweight pattern 을 활용해 보면 되겠지요.

http://en.wikipedia.org/wiki/State_pattern

Blog Logo

Ki Sung Bae


Published

Image

Gsong's Blog

Developer + Entrepreneur = Entreveloper

Back to Overview