Command Pattern
Command Pattern
개요
- 요청 내역을 객체로 객체로 캡슐화하여 서로 다른 요청 내역에 대해 매개변수화할 수 있게 하는 패턴.
- 요청을 큐에 저장하거나 로그를 남기거나 작업 취소 기능을 구현할 수 있다.
사용하는 경우
- 요청을 큐에 저장하거나 로그를 남기고 싶을 때
- 요청을 매개변수화하고 싶을 때
- 요청을 취소하고 싶을 때
- 요청을 실행하는 시점을 지연시키고 싶을 때
- 요청을 실행하는 시점을 변경하고 싶을 때
구성요소
- Command: 요청을 캡슐화하는 인터페이스
- ConcreteCommand: Command 인터페이스를 구현한 클래스
- Invoker: 요청을 호출하는 역할
- Receiver: 요청을 처리하는 역할
- Client: 요청을 생성하는 역할
Command 객체는 Invoker에 의해 호출되고, Invoker는 Command 객체를 통해 Receiver에게 요청을 전달한다.
예제
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <memory>
using namespace std;
class Receiver {
public:
virtual void action() = 0;
virtual ~Receiver() = default;
};
class ReceiverA : public Receiver {
public:
void action() override {
cout << "ReceiverA action" << endl;
}
};
class ReceiverB : public Receiver {
public:
void action() override {
cout << "ReceiverB action" << endl;
}
};
class Command {
public:
Command(Receiver& receiver) : receiver(receiver) {}
virtual void execute() = 0;
virtual ~Command() = default;
};
class ConcreteCommandA : public Command {
private:
Receiver receiver;
public:
void execute() override {
cout << "Executing ConcreteCommandA" << endl;
receiver.action();
}
};
class ConcreteCommandB : public Command {
private:
Receiver receiver;
public:
void execute() override {
cout << "Executing ConcreteCommandB" << endl;
receiver.action();
}
};
class Invoker {
private:
unique_ptr<Command> command;
public:
void setCommand(unique_ptr<Command> cmd) {
command = move(cmd);
}
void executeCommand() {
if (command) {
command->execute();
} else {
cout << "No command set" << endl;
}
}
};
int main() {
Invoker invoker;
ReceiverA receiverA;
unique_ptr<Command> commandA = make_unique<ConcreteCommandA>(receiverA);
invoker.setCommand(move(commandA));
invoker.executeCommand();
ReceiverB receiverB;
unique_ptr<Command> commandB = make_unique<ConcreteCommandB>(receiverB);
invoker.setCommand(move(commandB));
invoker.executeCommand();
return 0;
}
취소 기능
- Invoker에서 Command를 실행할 때 마지막으로 실행한 Command를 저장하고, 취소할 때는 저장된 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
class Command {
public:
virtual void undo() = 0; // 취소 메서드 추가
};
class ConcreteCommandA : public Command {
void undo() override {
cout << "Undoing ConcreteCommandA" << endl;
}
};
class Invoker {
private:
unique_ptr<Command[]> commands; // 명령어 배열
unique_ptr<Command> lastCommand; // 마지막 명령어 저장
public:
void executeCommand(int index) {
if (commands[index]) {
commands[index]->execute();
lastCommand = move(commands[index]); // 마지막 명령어 저장
} else {
cout << "No command set" << endl;
}
}
void undoCommand() {
if (lastCommand) {
lastCommand->undo(); // 마지막 명령어 취소
} else {
cout << "No command to undo" << endl;
}
}
};
매크로 커맨드
- 커맨드 객체로 구성된 매크로를 만들어서 여러 Command 객체를 하나의 Command로 묶어 실행할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class MacroCommand : public Command { private: vector<unique_ptr<Command>> commands; // Command 객체 배열 public: void addCommand(unique_ptr<Command> command) { commands.push_back(move(command)); } void execute() override { for (auto& command : commands) { command->execute(); } } };
커맨드 패턴 활용하기
- 스케쥴러, 스레드풀, 작업큐 활용하기: 커맨드로 컴퓨테이션의 한 부분(리시버와 일련의 행동)을 패키지로 묶어서 일급 객체 형태로 전달하여 스레드풀이나 작업큐에 넣어줄 수 있다. 스레드는 큐에서 커맨드를 하나씩 꺼내서 실행한다.
- 복구 기능: 디스크에 실행 내역을 저장하고, 어플리케이션이 다운되면 커맨드 객체를 다시 읽어와서 실행할 수 있다.
This post is licensed under CC BY 4.0 by the author.