[Design Pattern] 커맨드 패턴

Command Pattern

커맨드 패턴은 요청 자체를 캡슐화하는 패턴이다. 이를 통해 요청이 서로 다른 사용자를 매개변수로 만들고 요청을 대기시키거나 로깅하며 되돌릴 수 있는 연산을 지원한다.

활용성

수행할 동작의 매개변수화

수행할 동작을 매개변수화 할 수 있다. 절차지향 프로그래밍에서는 이를 콜백 함수 즉, 어딘가 등록되었다가 나중에 호출되는 함수를 사용해서 이런 매개변수화를 표현할 수 있다.

서로 다른 시간에 요청을 명시, 저장, 실행

커맨드 객체는 원래 요청과 다른 생명주기가 있다. 요청을 받아 처리하는 객체가 주소 지정 방식과는 독립적으로 표현될 수 있다면 커맨드 객체를 다른 프로세스에게 넘겨주고 거기서 해당 처리를 수행할 수 있다.

실행 취소 기능을 지원

커맨드의 Execute() 연산은 상태를 저장할 수 있는데 이를 통해 지금까지 얻은 결과를 바꿀 수 있다. Unexecute() 연산을 추가하면 된다.

재적용 가능하도록 지원

커맨드 인터페이스를 확장해서 load()와 store()연산을 정의하면 상태의 변화를 지속적으로 저장소에 저장해둘 수 있다. 이를 통해 시스템이 고장났을 때 재적용이 가능하도록 변경 과정에 대한 로깅을 지원하게 할 수 있다.

기본적인 연산 조합으로 만든 상위 수준 연산을 만듬

기본적인 연산의 조합으로 만든 상위 수준 연산을 써서 시스템을 구조화할 수 있다. 이런 시스템의 일반적 특성은 트랜잭션을 지원한다는 점이다.

커맨드 패턴 사용에 따른 결과

  1. 연산을 호출하는 객체와 연산 수행 방법을 구현하는 객체를 분리.
  2. 명령을 여러개 복합해 복합 명령을 만들 수 있다.
  3. 새로운 커맨드 객체를 추가하기 쉽다.

커맨드

구조

커맨드의 구조는 다음과 같다.

Command Pattern Diagram

C++ 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CommandPattern::buildMacro()
{
Receiver* receiver = new Receiver;
CommandComposite* keyboardInput1 = new InputCommandComposite(new KeyboardInputCommand("This is new Macro"));
CommandComposite* keyboardInput2 = new InputCommandComposite(new KeyboardInputCommand("Macro Start"));
CommandComposite* rightClick = new ClickCommandComposite(new RightClickCommand());
CommandComposite* keyboardInput3 = new InputCommandComposite(new KeyboardInputCommand("Macro End"));

receiver->addMacro(keyboardInput1);
receiver->addMacro(keyboardInput2);
receiver->addMacro(rightClick);
receiver->addMacro(keyboardInput3);

receiver->action();

receiver->removeMacro();
receiver->removeMacro();

receiver->action();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Receiver
{
std::vector<CommandComposite*> command;
public:
void addMacro(CommandComposite* composite)
{
command.emplace_back(composite);
}

void removeMacro()
{
command.pop_back();
}

void action()
{
for(CommandComposite* composite : command)
composite->execute();
}
};
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
class CommandComposite
{
public:
virtual ~CommandComposite() {}
virtual void execute() = 0;
};

class InputCommandComposite : public CommandComposite
{
InputCommand* command;
public:
InputCommandComposite(InputCommand* clickCommand) : command(clickCommand) {}

void execute() override
{
command->execute();
}
};

class ClickCommandComposite : public CommandComposite
{
ClickCommand* command;
public:
ClickCommandComposite(ClickCommand* clickCommand) : command(clickCommand) {}

void execute() override
{
command->execute();
}
};
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
class Command
{
public:
virtual ~Command() {}
virtual void execute() = 0;
};

class ClickCommand : public Command
{
public:
virtual ~ClickCommand() {};
};

class RightClickCommand : public ClickCommand
{
public:
void execute() override
{
std::cout<<"execute right click command"<<std::endl;
}
};

class InputCommand : public Command
{
public:
virtual ~InputCommand() {};
};

class KeyboardInputCommand : public InputCommand
{
std::string cmd;
public:
KeyboardInputCommand(std::string cmd) : cmd(cmd) {}

void execute() override
{
std::cout<<"execute keyboard command "<<cmd<<std::endl;
}
};

java 구현

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
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
CommandComposite inputCommandComposite1 =
new InputCommandComposite(new KeyboardInputCommand("This is new Macro"));
CommandComposite inputCommandComposite2 =
new InputCommandComposite(new KeyboardInputCommand("Macro Start"));
CommandComposite clickCommandComposite =
new ClickCommandComposite(new RightClickCommand());
CommandComposite inputCommandComposite3 =
new InputCommandComposite(new KeyboardInputCommand("Macro End"));

receiver.add(inputCommandComposite1);
receiver.add(inputCommandComposite2);
receiver.add(clickCommandComposite);
receiver.add(inputCommandComposite3);

receiver.action();

receiver.remove();
receiver.remove();

receiver.action();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Receiver {
private List<CommandComposite> commandComposites;

public Receiver() {
commandComposites = new ArrayList<>();
}

public void add(CommandComposite commandComposite) {
commandComposites.add(commandComposite);
}

public void remove() {
commandComposites.remove(commandComposites.size() - 1);
}

public void action() {
for(CommandComposite commandComposite : commandComposites)
commandComposite.execute();
}
}
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
public interface CommandComposite {
void execute();
}

public class InputCommandComposite implements CommandComposite{
private InputCommand command;

public InputCommandComposite(InputCommand command) {
this.command = command;
}

@Override
public void execute() {
command.execute();
}
}

public class ClickCommandComposite implements CommandComposite{
private ClickCommand command;

public ClickCommandComposite(ClickCommand command) {
this.command = command;
}

@Override
public void execute() {
command.execute();
}
}
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
public interface Command {
void execute();
}

public interface InputCommand extends Command{
}

public class KeyboardInputCommand implements InputCommand{
private String cmd;

KeyboardInputCommand(String cmd) {
this.cmd = cmd;
}

@Override
public void execute() {
System.out.println("execute keyboard command" + cmd);
}
}


public interface ClickCommand extends Command{
}

public class RightClickCommand implements ClickCommand{
@Override
public void execute() {
System.out.println("execute right click command");
}
}
Author: Song Hayoung
Link: https://songhayoung.github.io/2020/08/22/Design%20Pattern/CommandPattern/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.