본문 바로가기

PROGRAMMING/Design Pattern

[GoF] 명령 (Command) 패턴

명령 (Command) 패턴 행동 패턴

명령을 객체의 형태로 캡슐화하여 동적으로 명령을 수행할 수 있고, 명령을 저장함으로써 명령을 지연시키거나 되돌릴 수 있게 합니다.


 

게임 속 플레이어를 키보드나 조이스틱을 통해 조종해 본 경험이 있을 것이다. 유저가 WASD 키를 누르거나 조이스틱을 어느 방향으로 당긴다면, 게임 속 지정된 규칙에 따라 플레이어는 움직이게 된다. 게임을 프로그래밍적인 관점으로 본다면, 외부 입력을 제어하는 부분과 플레이어를 조종하는 부분은 분명히 따로 분리되어 있을 것이다.

하지만 단순히 외부 입력에서 함수를 호출함으로써 플레이어를 조종하게 하려면, 외부 입력을 제어하는 부분에선 게임 속의 특정한 플레이어(대상)를 알아야만 할 것이다. 서로 둘은 연관이 거의 없는데도 강하게 결합되어 있는지라 유연성이 떨어지니 같은 입력에 특정한 플레이어가 아닌 다른 작업을 수행하게 할려고 할 때 구현이 복잡해진다. 그래서 게임에서는, 외부 입력을 제어하는 부분을 플레이어를 조종하는 부분과의 강한 결합으로부터 떼어놓기 위해 명령 패턴을 사용할 수 있다.

명령 (Command) 패턴명령을 객체화하여 명령을 변형하거나 저장할 수 있게 한다. 프로그래밍적인 관점으로 명령은 단순히 함수의 호출이다. 그래서 명령 패턴은 함수의 호출을 객체 안으로 감싸게 된다. 클라이언트는 명령 객체에 명령이 호출 되었을 시 대상에 대해 어떤 작업을 수행하게 할지 정의하고, 명령을 호출하는 쪽은 명령을 수행할 대상을 특정할 필요 없이 추상화 된 명령 객체를 참조하기만 하면 명령 객체를 통해 대상에 대한 명령을 수행할 수 있게 한다.

또한, 여러 개의 명령 객체들을 묶어 하나의 복합적인 명령을 새로 만들 수도 있고(컴포지트 패턴이나, 책임 연쇄 패턴을 사용하여 명령들을 묶을 수 있다), 명령 객체는 저장할 수 있기 때문에 명령을 보류하다 클라이언트가 원하는 시점에 명령을 수행하거나, 실행했던 명령들을 기록할 수도 있다. 그리고 명령 객체에 되돌릴(Undo) 수 있는 기능을 정의한다면 기록된 명령들을 통해 대상을 명령을 수행하기 이전의 상태로 되돌릴 수도 있다.

명령 패턴을 사용한다면 명령을 호출하는 객체를 명령을 수행하는 객체로부터 분리하여 다양한 명령을 유연하게 수행할 수 있도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class PatternCommand
{
    public interface ICommand
    {
        void Execute();
    }
 
    public class CommandA : ICommand
    {
        private IReceiver _receiver;
 
        public CommandA(IReceiver receiver)
        {
            _receiver = receiver;
        }
 
        public void Execute()
        {
            _receiver.OperationA();
        }
    }
 
    public class CommandB : ICommand
    {
        private IReceiver _receiver;
 
        public CommandB(IReceiver receiver)
        {
            _receiver = receiver;
        }
 
        public void Execute()
        {
            _receiver.OperationB();
        }
    }
 
    public interface IReceiver
    {
        void OperationA();
        void OperationB();
    }
 
    public class Receiver : IReceiver
    {
        public void OperationA()
        {
            Console.WriteLine("Operate Receiver as A");
        }
 
        public void OperationB()
        {
            Console.WriteLine("Operate Receiver as B");
        }
    }
 
    public static void Main(string[] args)
    {
        IReceiver receiver = new Receiver();
 
        ICommand commandA = new CommandA(receiver);
        ICommand commandB = new CommandB(receiver);
 
        commandA.Execute();
        commandB.Execute();
    }
}
cs

1
2
3
Operate Receiver as A
Operate Receiver as B
Press any key to continue . . .
cs

위의 예제에서 CommandA 객체와 CommandB 객체는 명령이 호출 되었을 때 Receiver 객체에 대해 어떤 작업을 수행할지 다르게 정의되어 있다. 클라이언트는 이 두 객체를 ICommand 인터페이스로 묶어 동일하게 명령을 호출하였으나, 결과적으로 Receiver의 다른 함수를 호출하게 되었다.