State 패턴

디자인 패턴중에 매우 사용도가 높고 매우 효율적인 State 패턴은 공부를 하다보면 좀 헷갈리는 디자인 패턴입니다.

 

대부분의 패턴은 if 문과 같이, 유지보수나 버그를 양산하기 쉬운 코드를 없애고 쉬운 확장을 목적으로 구조를 만듭니다.

 

State 패턴은 각 상태가 바뀔 때 마다 늘어나는 if문을 없애는 패턴입니다. 즉, 상태들 끼리 서로의 상태를 바꿔가며 동작하기 때문에 유지보수도 쉽고 확장도 용이합니다. 그러나 상태변화에 대한 명확한 이해와 구조를 어떻게 할지에 대한 고민이 필요합니다.

 

다소 생소하고 복잡하게 보일지 모르지만 한번 구현해 보도록 하겠습니다.

 

예제는 단순한 Light를 On/Off하는 기능을 State 패턴으로 구현해 보았습니다. 실제로 구동해 본 코드는 아니기 때문에 쓱 읽기만 하시고 직접 구현하실 때는 반드시 테스트를 거친 후 적용 바랍니다.

 

먼제 헤더입니다. 불완전 선언(구조체 전방선언)으로 되어있습니다. 구조체 두개가 서로를 참조하기 때문입니다. 우선 상태(State) 구조체와 콘크리트(Concrete) 구조체 두가지가 있습니다.

/* hearder */
typedef struct _light_t light_t;
typedef struct _state_t state_t;

typedef struct _state_t
{
	bool (*on)(light_t *light);
	bool (*off)(light_t *light);
}_state_t;

typedef struct _light_t
{
	state_t *state;
	bool (*set_state)(state_t *state);
	bool (*on)(void);
	bool (*off)(void);
}_light_t;
 

모두가 알다시피 Light는 두가지의 상태를 가지고 있습니다. 따라서 두개의 상태 즉, On/Off 상태에 따라 처리하는 부분이 달라지게 됩니다.

 

예를 들면 on → off 상태로 변해야 하고 off → on 상태로도 변해야 합니다. 상태가 두가지 이기 때문에 if 문으로 구현하고 싶은 욕망이 생기겠지만 향후 상태가 더 많은 프로그램의 경우에는 if문이 더 많아 지겠지요? 이러한 if 문을 줄이기 위해 State 패턴이 사용됩니다.

 

다음 코드는 light.c 코드 입니다. 아래 코드에서는 먼저 light 를 만들어 주고 현재 상태를 assign해줍니다. 그리고 명령에 따라 현재 assign되어 있는 상태의 on/off를 실행 시키게 됩니다. 현재 default는 light off 상태입니다.

/* light.c */
static light_t this = {null};
static light_t *setup_light(void)
{
	this.on = light_on;
	this.off = light_off;
	this.set_state = set_light_state;
	set_light_state(get_state_off());

	return &this;
}

static void set_light_state(state_t *state)
{
	this->state = state;
}

static bool light_on(void)
{
	if(this->state == null)
		return false;

	return this->state->on(&this);
}

static bool light_off(void)
{
	if(this->state == null)
		return false;

	return this->state->off(&this);
}
 

헷갈리지 않게 먼저 main function 부터 볼까요? 단순 합니다. light를 assign 해주고 켰다 껐다를 반복해 줍니다. 예를 들어 light->on(); 을 해주면 결과적으로 현재 state의 on function을 실행하게 됩니다.

/* main.c */
void main(void)
{
	light_t *light = set_light();

	light->on();
	light->off();
	light->on();
	light->off();
}
 

이제 on/off가 어떻게 스위칭 되는지 state 쪽 코드들을 보도록 하겠습니다. 먼저 state_on.c 파일입니다.

/* state_on.c */
state_t this = {null};
state_t *get_state_on(void)
{
	this.on = state_on;
	this.off = state_off;

	return &this;
}

static bool state_on(light_t *light)
{
	if(this == null)
		return false;

	//light on process

	return true;
}

static bool state_off(light_t *light)
{
	if(this == null)
		return false;

	//light of process

	return light->set_state(get_state_off());
}
 

get_state_on() 은 현재 on state를 리턴합니다. 그리고 light->on을 하면 결과적으로 state_on 함수가 실행됩니다. 실제로 불빛을 끄고 싶으시면 GPIO를 내리든가 올리든가 해서 끄면 되겠지요? 만약 light->off 하면 불빛을 끄고 light의 상태를 off로 바꿔줍니다.

 

어느정도 state_off.c가 예상됐으리라 생각됩니다. 아래 코드를 보시겠습니다.

/* state_off.c */
state_t this = {null};
state_t *get_state_off(void)
{
	this.on = state_on;
	this.off = state_off;

	return &this;
}

static bool state_on(light_t *light)
{
	if(this == null)
		return false;

	//light on process

	return light->set_state(get_state_on());
}

static bool state_off(light_t *light)
{
	if(this == null)
		return false;

	//light of process

	return true;
}
 

state on 과 거의 비슷하지요? 마찬가지로 state off상태에서 state on을 해주면 light의 state를 on으로 변경해주게 됩니다.

 

상태는 상태 function에서 알아서 바꿔주기 때문에 if 문이 필요없게 됩니다. 지금의 예제는 두개의 상태로 되어있는 경우로 되어 있지만 오히려 상태가 많은 경우나 늘어나는 경우에 State 패턴은 더 큰 힘을 발휘하게 됩니다.

 

즐거운 c 생활하세요!