지난 포스팅에서 디자인패턴의 정의 및 특징을 비롯한 디자인패턴 종류들에 대해 알아보았습니다.

디자인패턴 정의 및 종류

 

이번 포스팅에서는 게임 클라이언트 개발 시 자주 사용하는 디자인 패턴중 하나인 커맨드 패턴에 대해서 알아보겠습니다.

 

커맨드 패턴

  • 요청 자체를 캡슐화 하는패턴으로, 서로 다른 사용자 (Ivoker-Receiver)를 매개변수로 만들고, 요청을 대기시키거나 로깅하며 되돌릴 수 있는 연산을 지원한다.
  • 어떤 행동을 나타내는 객체를 변수에 할당(캡슐화)하여 유동적으로 교체 할 수 있게 한다.
  • 행동 패턴(Behavioral Patterns)중 하나.
    • 객체나 클래스 사이의 알고리즘 혹은 책임 분배에 관련된 패턴이다.
    • 객체 사이의 결합도를 최소화 하는것에 중점을 가지고 있다.

 

장점 및 단점

  • 호출시 Invoker 객체에서 실행만 하는 구조로 설계되어 클래스간의 책임이 분명해진다. - SRP
  • 수정 시 기존의 코드를 손상하지 않고 새 커맨드들을 도입할 수 있다. - OCP
  • 유연성과 확장성을 높이고 게임 내 다양한 명령을 객체화하여 관리할 수 있다.
  • 커맨드 패턴은 각 명령마다 별도의 클래스가 필요하므로 복잡성이 증가한다.
  • 불필요한 객체화 시 코드 효율성이 떨어진다.

 

사용 예시

  • 입력 키 변경시
    • 사용자(Client)들의 입력하는 키들의 값을 바꿀 수 있게 해준다.
    • 게임 UI버튼과 상호작용 시.
  • Undo/Redo
    • 게임에서 발생된 이벤트를 되돌리거나 다시 실행 시킬 때
    • 싱글 턴제 게임에서 행동 되돌리기 등에 사용한다.

 

예시코드

 

1. 캡슐화 하기위한 인터페이스 Command 클래스.

// 구현 클래스들을 캡슐화 하기위한 추상 클래스
// Invoker와 Receiver가 직접 연결되어있지 않은 낮은 결합도 유지
class Command {
public:
	virtual ~Command() = default;
	virtual void Execute() = 0; 
};

 

2.  실제 동작을 담당하는 Receiver 구현.

// Receiver - 구현 클래스. 실제 동작
// Command를 상속받은 구현클래스. 특정 행동에 대한 실행 로직들을 캡슐화 한다.
class Skill1 : public Command {
public:
	void Execute() override {
		// Skill 1 관련 로직
	}
};

class Skill2 : public Command {
public:
	void Execute() override {
		// Skill 2 관련 로직
	}
};

 

3.  Client의 입력을 받으며, 입력받는 Key와 해당하는 스킬들을 매핑하는 Invoker

// Invoker - 사용자의 입력을 받고, Command의 실제 동작을 담당한다.
class InputHandler {
public :
	// std::unique_ptr == UE5 - TUniquePtr
	void BindKey(char Key, std::unique_ptr<Command> command) {
		keyBindings_[Key] = std::move(command);
	}

	void HandleInput(char key) {
		auto it = keyBindings_.find(key);
		if (it != keyBindings_.end() && it->second) {
			it->second->Execute();	// 해당 Key에 매핑된 행동 구현 클래스 실행
		}
	}

private:
	// 입력 Key와 행동 Skill 클래스를 매핑하기위한 UnorderedMap
	std::unordered_map<char, std::unique_ptr<Command>> keyBindings_;
};

 

4. Client는 매핑된 Key를 입력하면 그에 맞는 Receiver를 전달받으며, 수정시에도 BindKey를 사용한 할당만 하면 된다.

-> 느슨한 결합을 유지하고 있으며, 유지보수성이 높다.

	InputHandler inputHandler;

	// Client - 각 Key에맞는 Command를 바인딩한다.
	inputHandler.BindKey('Q', std::make_unique<Skill1>());
	inputHandler.BindKey('W', std::make_unique<Skill2>());

	if (IE_Pressed == 'Q')
	{
		inputHandler.HandleInput('Q');
	}
	else if (IE_Pressed == 'W')
	{
		inputHandler.HandleInput('W');
	}
	

 

 

정리

  • 디자인패턴 Behavioral Pattern중 하나.
  • 사용자의 입력을 받는 Invoker, 실제 동작 Receiver을 가지고있으며 인터페이스를 통해 캡슐화를 유지한다.
  • 수정 시 다른 코드에 영향을 주지 않는다. - OCP
  • 호출 시 Invoker는 그에 맞는 Receiver를 호출, Receiver는 실행만 담당한다. - SRP
  • 각 커맨드마다 클래스가 추가되므로 코드 내 복잡성이 증가할 수 있다.

+ Recent posts