본문 바로가기
컴퓨터 과학/디자인패턴

[디자인패턴] 행위 패턴(2) : 커맨드(Command)

by webcodur 2024. 3. 25.
728x90
반응형

목차

     

     

    커맨드(Command)

    커맨드 디자인 패턴은 행동 디자인 패턴의 한 종류로, 요청 자체를 캡슐화하는 방식이다. 이 패턴의 목적은 요청을 발생시키는 객체와 요청을 처리하는 객체 사이의 결합도를 줄이는 것이다. 이를 통해 요청을 큐에 저장하거나, 로그로 기록하고, 작업을 취소할 수 있는 유연성을 제공한다.

    "커맨드(Command)"라는 용어는 말 그대로 '명령'을 의미한다. 이 패턴에서 '명령'은 수행될 모든 동작(작업)을 객체로 캡슐화하는 것을 말한다. 객체 지향 프로그래밍에서는 이러한 명령을 객체의 형태로 표현하여, 실행할 작업과 관련된 모든 정보를 함께 묶어 관리한다.

     

    패턴 미적용 예시 코드

    게임 개발에 있어서, 커맨드 패턴을 적용하지 않고 직접 객체의 메소드를 호출하여 명령을 수행하는 방식은, 객체 간의 강한 결합을 초래하고, 유연성과 확장성을 떨어뜨린다. 아래는 간단한 게임 컨트롤을 예로 든 코드이다.

    // 게임 캐릭터
    public class GameCharacter
    {
        public void MoveForward()
        {
            Console.WriteLine("앞으로 이동한다");
        }
        public void MoveBackward()
        {
            Console.WriteLine("뒤로 이동한다");
        }
        // 추가 동작들...
    }
    
    // 게임 컨트롤
    public class GameControl
    {
        private GameCharacter character;
    
        public GameControl(GameCharacter character)
        {
            this.character = character;
        }
    
        public void InputHandler(string command)
        {
            switch (command)
            {
                case "forward":
                    character.MoveForward();
                    break;
                case "backward":
                    character.MoveBackward();
                    break;
                // 추가 명령 처리...
            }
        }
    }
    

    문제점

    • GameControl 클래스는 GameCharacter 클래스의 메소드에 직접적으로 의존하므로, GameCharacter에 변화가 생길 때마다 GameControl  도 수정해야 한다.
    • 새 동작을 추가하거나 기존 동작을 변경하려면 GameCharacterGameControl 두 클래스 모두를 수정해야 한다.
    • 명령을 취소하거나 로그를 기록하는 등의 추가 기능 구현이 어렵다.

     

     

    패턴 적용 예시 코드

    커맨드 패턴을 적용하면, 객체 간 결합도를 낮추고, 명령을 객체로 캡슐화하여 유연성과 확장성을 높일 수 있다. 커맨드 패턴의 구성 요소를 사용해 게임 개발에서 커맨드 패턴을 적용해 보자

     

    커맨드 패턴의 기본 구성 요소

    1. Command: 실행될 모든 명령에 대한 인터페이스. 이 인터페이스는 명령을 실행하는 데 필요한 execute() 메소드를 정의한다.
    2. ConcreteCommand: Command 인터페이스를 구현하는 클래스. 구체적인 동작(명령)을 구현한다.
    3. Invoker: Command 객체를 사용해 요청을 실행하는 역할을 한다. 사용자의 요청에 따라 해당 명령을 실행한다.
    4. Receiver: ConcreteCommand 에 의해 수행될 연산을 구현한다. 실질적인 작업 수행자.
    5. Client: ConcreteCommand 객체를 생성하고, 수행될 작업을 Receiver 와 연결한다. 필요에 따라 Invoker 에 명령을 할당한다.

    먼저 Command 인터페이스와 몇 가지 구체적인 명령(ConcreteCommand) 클래스를 정의한다. 그 다음에는 명령을 수행할 Receiver 클래스, 명령을 호출할 Invoker 클래스, 그리고 이 모든 것을 설정하는 Client 코드를 구현한다.

     

    // Command 인터페이스
    public interface ICommand
    {
        void Execute();
    }
    
    // ConcreteCommand 클래스
    public class MoveForwardCommand : ICommand
    {
        private GameCharacter _character;
    
        public MoveForwardCommand(GameCharacter character)
        {
            _character = character;
        }
    
        public void Execute()
        {
            _character.MoveForward();
        }
    }
    
    public class MoveBackwardCommand : ICommand
    {
        private GameCharacter _character;
    
        public MoveBackwardCommand(GameCharacter character)
        {
            _character = character;
        }
    
        public void Execute()
        {
            _character.MoveBackward();
        }
    }
    
    // Receiver 클래스
    public class GameCharacter
    {
        public void MoveForward()
        {
            Console.WriteLine("앞으로 이동한다");
        }
    
        public void MoveBackward()
        {
            Console.WriteLine("뒤로 이동한다");
        }
    
        // 추가 동작들...
    }
    
    // Invoker 클래스
    public class InputHandler
    {
        private IDictionary<string, ICommand> _commands;
    
        public InputHandler()
        {
            _commands = new Dictionary<string, ICommand>();
        }
    
        public void SetCommand(string key, ICommand command)
        {
            _commands[key] = command;
        }
    
        public void ExecuteCommand(string key)
        {
            if (_commands.ContainsKey(key))
            {
                _commands[key].Execute();
            }
        }
    }
    
    // Client 코드
    class Program
    {
        static void Main(string[] args)
        {
            GameCharacter character = new GameCharacter();
            InputHandler inputHandler = new InputHandler();
    
            // 커맨드와 리시버 연결
            inputHandler.SetCommand("forward", new MoveForwardCommand(character));
            inputHandler.SetCommand("backward", new MoveBackwardCommand(character));
    
            // 입력에 따라 명령 실행
            inputHandler.ExecuteCommand("forward");
            inputHandler.ExecuteCommand("backward");
        }
    }
    

     

    커맨드 패턴의 이점

    • 결합도 감소: InputHandlerGameCharacter 의 메소드를 직접 호출하지 않으므로, 캐릭터 클래스에 변경이 생겨도 입력 처리 로직을 변경할 필요가 없다.
    • 확장성: 새로운 명령을 추가하기 위해선 단지 ICommand 인터페이스를 구현하는 새 클래스를 추가하고, 이를 InputHandler 에 등록하기만 하면 된다.
    • 재사용성과 유연성: 명령 객체를 재사용하여 다른 컨텍스트에서 같은 작업을 수행할 수 있고, 실행 시점을 조절하여 undo/redo 같은 기능을 구현하는 것이 용이해진다.
    반응형