커맨드 패턴

송칭·2024년 11월 13일
0

디자인패턴

목록 보기
8/8

커맨드 패턴은 사용자가 보낸 요청 자체를 객체로 캡슐화하여 요청에 필요한 정보를 저장하거나 기록하여 나중에 재사용 가능하도록 하는 디자인패턴 중 하나다.

기존에는 객체가 할 수 있는 동작을 함수로 정의하고 호출하는 식으로 특정 동작을 시켜왔다.
커맨드 패턴을 활용하면 이런 동작을 객체로 표현하게 되어서 모듈화하여 저장하고 관리할 수 있는데 이는 Ctrl+Z나 Ctrl+Y 같은 Undo / Redo 기능을 만들어야하거나 사용자가 보낸 요청을 기록하여 순차적으로 실행하고, 다양한 동작을 동적으로 조합하여 실행하기에 매우 유용하다.

Command 인터페이스
모든 구체적인 명령어들이 동일한 방식으로 호출되도록 정의하는 인터페이스

public interface ICommand
{
    void Execute(); // 실행
    void Cancel(); // 실행 취소
    void Undo(); // 실행 복구
}

만일 명령에 공통적으로 현재 동작이 가능한지 알려주는 bool CanExecute()가 필요하다면 여기에 먼저 추가를 해주어야 할 것이다.

ConcreteCommand
위의 Command 인터페이스를 구현하여 실제 동작을 표현하는 클래스

public class MoveCommand : ICommand
{
    private Character character; 
    private Vector3 targetPos; // 목표 좌표
    private Vector3 prePos; // 명령어를 시작하기 직전의 좌표

    public MoveCommand(Character character, Vector3 targetPos)
    {
    	prePos = character.transform.position;
        this.character = character;
        this.targetPos = targetPos;
    }

    public void Execute()
    {
        character.Move(targetPos); // 목표 위치로
    }

	public void Cancel()
    {
        character.MoveStop(); // 현재 위치에서 멈춤
    }
    
    public void Undo()
    {
        character.Move(prePos); // 원래 위치로
    }
}

public class AttackCommand : ICommand
{
    private Character character;

    public AttackCommand(Character character)
    {
        this.character = character;
    }

    public void Execute()
    {
        character.Attack(); // 공격
    }

    public void Cancel()
    {
        character.CancelAttack(); // 공격 캔슬
    }
    
    public void Undo()
    {
    	// 굳이 공격 동작을 되돌릴 이유가 있나?
    }
}

Invoker
사용자의 요청을 Command 객체로 변환하여 저장하고 실행하는 역할

public class InputManager
{
	// 실행되었던 명령어를 저장할 큐
	private Stack<ICommand> cmdHistory = new Stack<ICommand>(); 
    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        cmdHistory.Push(command);
    }
    
    public void UndoCommand() // 실행취소
    {
        if (commandHistory.Count > 0)
        {
            var command = commandHistory.Pop();
            command.Undo();
        }
    }
}

큐를 저장할 컬렉션은 List, Queue, Stack 등 다양하며, 물론 저장해두고 특정한 시점에 동시에 실행되게 할 수도 있다.

UndoCommand()를 통해 했던 동작을 역으로 되돌리는 것도 충분히 가능하다.

Receiver
명령 수행에 필요한 실제 작업을 구현한 객체

public class Character
{
	public void Move(Vector3 pos)
    {
    	Debug.Log($"{pos}로 이동합니다");
    }
    
    public void StopMove()
    {
    	Debug.Log($"{transform.position}에서 멈춥니다");
    }
    
    public void Attack()
    {
    	Debug.Log("공격!");
    }
    
    public void CancelAttack()
    {
    	Debug.Log("공격 취소!");
    }
}

모든 디자인 패턴이 그렇듯 커맨드 패턴도 적재적소에 사용하지 않으면 코드가 복잡해질 수 있다. 간단한 게임에서는 굳이 커맨드 패턴을 적용하기보다 기존처럼 함수 호출로 동작을 관리하는 편이 오히려 더 효율적이며, 커맨드 패턴은 특히 다양한 동작 기록 및 실행 취소, 재실행 요구가 있는 복잡한 시스템에 사용하는 것이 적절하다.

profile
게임 클라이언트

0개의 댓글