본문 바로가기
디자인패턴

[내가 생각한 패턴] 명령패턴, 뭘 하려거든 눈에 보이게 해

by PainDiver 2025. 2. 12.

프로그래밍에는 함수라는 개념이 있다

어떤 행위를 정의하고 그 행위를 특정시점에 호출하는 식으로 사용을 한다. 

 

만약 함수를 우리가 키 입력을 받았을때, 특정 타이밍에 호출해야한다고 한다면 

가장 나이브한 방법으로는 아래와 같은 코드를 짤 것 이다.

void Jump()
{
	// 폴짝!
}

int main()
{
	while(true)
	{
		Key key = GetKey();
		if(key == 스페이스바)
		{
			Jump();
		}
	}
    return 0;
}

 

사실 키 변경을 할거아니면 딱히 상관없는 로직이다. 작동은 한다!

작동은하는데.. 나중에 space를 눌렀는데 공격이 나오게 바꾸고 싶다면?

정말 아쉬운코드가 되겠다. 지금이야 space하나지만 입력이 여러개 생긴다면 꽤 귀찮은 일이 발생할것이다.

 

이 때 아주 딱 쓰기좋은 패턴이 하나 있다.


명령패턴

 

일단 내가 임의로 정의를 내려본다면

 

"명령,행동 자체를 함수객체 같이 구현부를 변수로 가진 객체로 만들어서 관리하는 방식으로 명령 구현부의 유연함과 객체로써 관리가능한 점을 이용하여 호출부와 구현부의 분리가 가능해지며 실행을 단위별로 관리하는게 가능해지는 패턴" 이라고 설명을 하고싶다.

 

사실 글로 잘 이해는 안되는것 같다. 명령을 어떻게 객체화해서 관리한다는 것인가?

코드를 살펴보자

 

class InputCommand()
{
public:
	virtual void Execute()
    { 
    	for_each(Actions.begin(),Actions.end(),
        [](const auto& action)
        {
            action();
        }); 
    };
    
    void BindAction(function<void()>&& action)
    {
    	Actions.push_back(move(action));
    }
    
private:
	vector<function<void()>> Actions;
}

void Jump()
{
	// 폴짝!
}

unordered_map<Key,ActionCommand> KeyBindings;

int main()
{
	InputCommand SpaceCommand;
	SpaceCommand.Bind([](){Jump()});    
	KeyBindings.Add(스페이스바,SpaceCommand);

	while(true)
	{
		Key keyPressed = GetKey();
		if(KeyBindings.find(KeyPressed) != KeyBindings.end())
		{
			KeyBindings[KeyPressed].Execute()
		}
	}
    return 0;
}

 

키에 행동을 담는 객체를 바인딩하여 키가 입력될때마다 바인딩된 함수를 호출하는 구조이다.

사실 위 코드랑 하는일은 똑같다.

그러나 명령을 담는 변수를 가진 객체를 키에 바인딩함으로써, 호출부와 구현부의 사이를 갈라놓았다.

바인딩만 해놓으면 호출부에서 어떤 함수가 사용될지 모른다.

 

 

게임 프로그래밍 패턴 이라는 책에서는 이 명령패턴을 가지고 undo를 설명한다.

책에서 나오는 내용대로 하려면 Command객체를 컨테이너에서 관리하는 방식으로하면 된다.

책에서 나오는 코드를 직접쓴건 아니고 한번 설명대로 코드를 대충 만들어봤다.

class Command
{
    virtual Execute() = 0;
    virutal Undo() = 0;
};

class MoveCommand : public Command
{
public:
    virtual void Execute()
    {
        MoveAction(MoveDistance);
    } override;

    virtual void Undo()
    {
        MoveAction(-MoveDistance);
    }override;

    void Bind(function<void(int)>&& func,const int& Distance)
    {
        MoveAction = move(func);
        MoveDistance = Distance;
    }

private:
    int MoveDistance;
    function<void(int)> MoveAction;
}

Stack<Command> CommandHistory;

void Undo()
{
    Command& cmd = commandHistory.pop();
    Cmd.Undo();
}

int main()
{
	
    // 5칸 움직였다!
    Command([](){Move();}, 5);
    CommandHistory.push()

    // 2칸 움직였다!
    Command([](){Move();}, 2);
    CommandHistory.push()

    // 제자리
    Undo();
    Undo();	
}

 

 

내 코드와는 구조가 다르지만 명령을 객체로 해서 함수호출을 변수화해서 다룬다는점은 내 생각이랑 같은것같다.

그리고 그게 명령패턴의 핵심인것같다.

 

구현부와 호출부의 디커플링 목적은 달성했으니 만족한다.

물론 실 프로젝트 들어가면 더 첨예하게 만들겠지만 예제코드는 설명만 가능할정도로만 만들었다.

 

뭘 하려거든(명령,함수호출) 눈에 보이게해(관리 가능하게 객체화)