상태(State) 패턴

김영웅·2025년 2월 27일

상태 패턴은 전략 패턴과 유사하게 행동을 캡슐화 시켰다는 점에서 유사하다.
하지만 전략 패턴과 다르게 상태 패턴은 객체 내부 상태에 따라 행동이 바뀌고, 상태가 서로 전환된다는 점에 집중한다

공통점

  1. 행동 캡슐화
    두 패턴 모두, 특정 로직(행동)을 인터페이스/추상 클래스 뒤에 감추고(캡슐화하고), 컨텍스트(Context)에서 해당 로직을 호출하는 구조를 가진다.
  2. 인터페이스 기반 설계
    컨텍스트가 구체 구현이 아닌 인터페이스(또는 추상 클래스)에 의존하므로, 구체적인 로직을 바꿔도(새로운 클래스 추가) 컨텍스트의 수정이 최소화된다.

차이점

  • 전략 패턴(Strategy)
    • 알고리즘(전략)을 캡슐화하여, 필요에 따라 런타임에 교체 가능
    • 주로 상태 전환 이라는 개념 없이, '현재 어떤 전략을 쓰는지'에 초점
    • 예: 정렬 알고리즘 교체, 할인 정책 교체 등
  • 상태 패턴(State)
    • 객체 내부 상태에 따라 행동이 달라지고, 행동 후 상태가 바뀌는 흐름에 주목
    • 상태가 바뀌면, 같은 메서드 호출도 서로 다른 로직을 수행
    • 예: 자판기에서 동전이 있으면 '동전 반환' 로직, 없으면 '반환 불가' 로직 등

구현

#include <iostream>
#include <Windows.h>
#include <time.h>

using namespace std;

class IState;
class IdleState;
class WalkState;
class RunState;
class AttackState;


class Player
{
	IState* state;

public:
	inline IState* GetState() { return state; }
	void ChangeState(IState* changeState);
};

enum class EKeyCode
{
	Walk,
	Sprint,
	Attack,
};

#pragma region State

class IState
{
public:
	virtual void EnterState(Player* player) = 0;
	virtual void HandleInput(Player* player, EKeyCode inputKey) = 0;
	virtual void Update(Player* player) = 0;
};

class IdleState : public IState
{
public:
	void EnterState(Player* player) override;
	void HandleInput(Player* player, EKeyCode inputKey) override;
	void Update(Player* player) override;
};


class WalkState : public IState
{
public:
	void EnterState(Player* player) override;
	void HandleInput(Player* player, EKeyCode inputKey) override;
	void Update(Player* player) override;
};

class RunState : public IState
{
public:
	void EnterState(Player* player) override;
	void HandleInput(Player* player, EKeyCode inputKey) override;
	void Update(Player* player) override;
};

class AttackState : public IState
{
public:
	void EnterState(Player* player) override;
	void HandleInput(Player* player, EKeyCode inputKey) override;
	void Update(Player* player) override;
};


#pragma endregion

#pragma region State_Cpp

void IdleState::EnterState(Player* player)
{
	cout << "Idle 상태 진입" << endl;
}

void IdleState::HandleInput(Player* player, EKeyCode inputKey)
{
	switch (inputKey)
	{
	case EKeyCode::Walk:
		cout << "걸어가기 시작합니다." << endl;
		player->ChangeState(new WalkState());
		break;
	case EKeyCode::Sprint:
		cout << "달리기 시작합니다" << endl;
		player->ChangeState(new RunState());
		break;
	case EKeyCode::Attack:
		cout << "공격합니다" << endl;
		player->ChangeState(new AttackState());
		break;
	}
}

void IdleState::Update(Player* player)
{
	cout << "가만히 있습니다." << endl;
}

//==============================================================================

void WalkState::EnterState(Player* player)
{
	cout << "Walk 상태 진입" << endl;
}

void WalkState::HandleInput(Player* player, EKeyCode inputKey)
{
	switch (inputKey)
	{
	case EKeyCode::Walk:
		cout << "이미 걷는 중입니다." << endl;
		break;
	case EKeyCode::Sprint:
		cout << "달리기 시작합니다" << endl;
		player->ChangeState(new RunState());
		break;
	case EKeyCode::Attack:
		cout << "걷기를 멈추고 공격합니다" << endl;
		player->ChangeState(new AttackState);
		break;
	}
}

void WalkState::Update(Player* player)
{
	cout << "걷고있습니다." << endl;
}

//==============================================================================

void RunState::EnterState(Player* player)
{
	cout << "Run 상태 진입" << endl;
}

void RunState::HandleInput(Player* player, EKeyCode inputKey)
{
	switch (inputKey)
	{
	case EKeyCode::Walk:
		cout << "다시 걷기 시작합니다." << endl;
		player->ChangeState(new WalkState());
		break;
	case EKeyCode::Sprint:
		cout << "이미 달리는 중입니다." << endl;
		break;
	case EKeyCode::Attack:
		cout << "달리기를 멈추고 공격합니다." << endl;
		player->ChangeState(new AttackState());
		break;
	}
}

void RunState::Update(Player* player)
{
	cout << "달리고 있습니다!!" << endl;
}

//==============================================================================

void AttackState::EnterState(Player* player)
{
	cout << "Attack 상태 진입" << endl;
}

void AttackState::HandleInput(Player* player, EKeyCode inputKey)
{
	switch (inputKey)
	{
	case EKeyCode::Walk:
		cout << "공격 중이라 못 걷습니다." << endl;
		break;
	case EKeyCode::Sprint:
		cout << "공격 중이라 달리지 못합니다." << endl;
		break;
	case EKeyCode::Attack:
		cout << "공격을 중지합니다." << endl;
		player->ChangeState(new IdleState());
		break;
	}
}

void AttackState::Update(Player* player)
{
	cout << "공격 중입니다." << endl;
}

#pragma endregion

void Player::ChangeState(IState* changeState)
{
	if (state) delete state;
	state = changeState;
}




int main()
{
	Player* player = new Player();
	player->ChangeState(new IdleState());

	while (true)
	{
		Sleep(1000);

		srand(time(NULL));
		int random = rand() % 3;
		
		player->GetState()->HandleInput(player, (EKeyCode)random);
		player->GetState()->Update(player);

		cout << endl << endl;
	}

	return 0;
}
profile
게임 프로그래머

0개의 댓글