명령 패턴

1 minute to read

개요

요청 자체를 캡슐화 하는 것입니다. 이를 통해 요청이 서로 다른 사용자를 매개변수로 만들고, 요청을 대기시키거나 로깅하며, 되돌릴 수 있는 연산을 지원합니다. -GoF의 디자인 패턴, 311쪽

동기

모든 게임에서는 유저의 입력을 받는 코드가 있습니다. 이런 코드는 입력을 받아 게임에서 의미 있는 행동으로 전환을 합니다. 많은 게임들은 키를 바꿀 수 있기 때문에 행동에 해당하는 함수를 직접 호출하지 말고 교체 가능한 무엇인가로 바꿔야 합니다.

결과

장점

메서드를 직접 호출하는 형태의 강한 커플링을 제거

단점

상태 없이 순수하게 행위만 정의된 경우, 모든 인스턴스가 같기 때문에 여러 개 만들어도 메모리만 낭비하게 됩니다. 이런 경우에는 경량 패턴을 사용하여 해결할 수 있습니다.

예제

class Command {
public:
	virtual ~Command() {}
	virtual void execute() = 0;
};

class JumpCommand : public Command {
public:
	virtual void execute() { jump(); }
};

class FireCommand : public Command {
public:
	virtual void execute() { fireGun(); }
};

//입력 핸들러 코드에는 각 버튼별로 Command 클래스 포인터를 저장합니다.
class InputHandler {
public:
	void handleInput();
private:
	Command* buttonX_;
	Command* buttonY_;
	Command* buttonA_;
	Command* buttonB_;
};

//입력 처리는 다음 코드로 위임된다.
void InputHandler::handleInput() {
	if(isPressed(ButtonX_)) buttonX_->execute();
	else if(isPressed(ButtonY_)) buttonY_->execute();
	else if(isPressed(ButtonA_)) buttonA_->execute();
	else if(isPressed(ButtonB_)) buttonB_->execute();
}

입력 처리에서 직접 함수를 호출하던 코드 대신, 한 겹 우회하는 계층이 생겼다. 여기에 플레이어뿐만 아니라 다른 캐릭터들도 움직이게 변형 할 수 있다.

class Command {
public:
    virtual ~Command() {}
    virtual void execute(GameActor& actor) = 0;
};

class JumpCommand : public Commnad {
public:
    virtual void execute(GameActor& actor) {
        actor.jump();
    }
};

Command* InputHandler::handleInput() {
    if(isPressed(ButtonX)) return buttonX_;
    if(isPressed(ButtonY)) return buttonY_;
    if(isPressed(ButtonA)) return buttonA_;
    if(isPressed(ButtonB)) return buttonB_;
    
    return NULL;
}
//어떤 액터를 매개변수로 넘겨줘야 할지 모르기 때문에 handleInput()에서는 명령을 실행할 수 없다.
//다음으로 명령 객체를 받아서 플레이어를 대표하는 GameActor 객체에 적용하는 코드가 필요하다.
Command* command = inputHandler.handleInput();
if(command) {
    command->execute(actor);
}
//명령을 실행할 때 액터만 바꾸면 플레이어가 게임에 있는 어떤 액터라도 제어할 수 있게 되었다.