디자인패턴
Command Pattern
KimGeon-U
2024. 12. 23. 17:51
지난 포스팅에서 디자인패턴의 정의 및 특징을 비롯한 디자인패턴 종류들에 대해 알아보았습니다.
이번 포스팅에서는 게임 클라이언트 개발 시 자주 사용하는 디자인 패턴중 하나인 커맨드 패턴에 대해서 알아보겠습니다.
커맨드 패턴
- 요청 자체를 캡슐화 하는패턴으로, 서로 다른 사용자 (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
- 각 커맨드마다 클래스가 추가되므로 코드 내 복잡성이 증가할 수 있다.