> For the complete documentation index, see [llms.txt](https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/system-design/design-pattern/undefined-1.md).

# 커맨드 패턴

## 커맨드 패턴

> 요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화할 수 있다.
>
> 이러면 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있다.

* **명령을 추상화해서 객체로 사용한다.**
* 클라이언트에서 커맨드 객체 생성
  * 커맨드 객체에는 행동과 리시버(receiver)의 정보가 같이 들어있음
  * 커맨드 객체에서 제공하는 메소드는 `excute()` 하나뿐
    * 행동을 캡슐화하며 리시버에 있는 특정 행동을 처리한다.
* 인보커(invoker) 객체의 `setCommand()` 메서드에 커맨드 객체를 넘겨서 호출
  * 넘긴 커맨드객체를 사용하기 전까지 인보커 객체에서 보관
  * ex: `remoteControl.setCommand(0, partyOnMacro, partyOffMacro)`
* 인보커에서 커맨드 객체의 `excute()` 메서드 호출
  * 리시버에 있는 행동 메서드가 호출됨

### 키워드

* **인보커(Invoker)**: 명령이 들어있으며, `execute()` 메서드를 호출함으로써 커맨드 객체에게 특정 작업을 수행하도록 요청
  * **작업 요구 담당 (종업원)**
  * **setCommand**
* **커맨드(Command)**: 커맨드 객체에서 구현해야 하는 인터페이스,
  * **execute**: 리시버에 특정 작업을 처리하라는 지시를 전달
  * **undo**: 작업 취소
* **리시버(Receiver)**:  요구사항을 수행할 때 어떤일을 처리해야 하는지 알고 있는 객체
  * **작업 처리 담당 (주방장)**
  * **action**
* **커맨드 객체(ConcreteCommand)**: 특정 행동과 리시버를 연결해줌
  * **주문서**&#x20;
  * **execute**: 리시버에 있는 메서드를 호출해서 요청된 작업을 처리
  * **undo:** 실행했던 작업을 원복 on <--> off

### **핵심 정리**

* 커맨드 패턴을 사용하면 **요청하는 객체**와 **요청을 수행하는 객체를 분리** 가능
* 분리하는 과정에 중심에는 커맨드 객체가 있고,  행동이 들어있는 리시버를 캡슐화함
* 인보커는 무언가 요청할 때 커맨드 객체의 `execute()` 메서드를 호출하면 됨
* 커맨드는 인보커를 매개변수화할 수 있음&#x20;
  * 실행 중에 동적으로 매개변수화를 설정할 수도 있음
* `execute()` 메서드가 마지막으로 호출되기 전의 상태로 되돌리는 작업 취소 메서드를 구현하면 커맨드 패턴으로 작업 취소 기능을 구현 가능
* 매크로 커맨드는 커맨드를 확장해서 여러 커맨드를 한 번에 호출할 수 있게 해주는 가장 간편한 방법
* 요청을 스스로 처리하는 '스마트;커맨드 객체를 사용하는 경우도 종종 있음
* 커맨드 패턴을 활용해서 로그 및 트랜잭션 시스템을 구현할 수도 있음

### 커맨드 객체 만들기

* 커맨드 객체는 모두 같은 인터페이스를 구현해야 함
* 오직 `excute()` 메서드를 하나만 갖고 있음

```typescript
public interface Command {
  public void execute();
}
```

```java
public class LighOnCommand implments Command {
  Light light;

  // 생성자에 커맨드 객체로 제어할 특정 조명의 정보가 전달됨
  // execute() 메서드가 호출되면 light 객체가 바로 그 요청의 리시버가 된다.  
  public LightOnCommand(Light light) {
    this.light = light;
  }
  
  public void execute() {
    light.on();
  }
  
  public void undo() {
    light.off();
  }
}
```

* 커맨드 객체를 사용해서 리모콘 만들기

```java
public class SimpleRemoteControl {
  Command slot;
  public SimpleReomteControl() {}
  
  public void setCommand(Command command) {
    slot = command;
  }
  public void buttonWasPressed() {
    slot.execute();
  }
}
```

### NoCommand 객체

* 커맨드 인터페이스에 따르고 excute() 실행시 아무 동작도 하지 않는 커맨드 객체
* 일종의 널 객체
  * 딱히 리턴할 객체도 없고 클라이언트가 null을 처리하지 않게 하고 싶을 때 활용
* 명령이 아직 할당되지 않는 부분에 NoCommand 객체를 넣어서 execute()가 호출되어도 문제가 생기지 않도록

### 작업 취소 기능 추가하기

```java
public interface Command {
  public void execute();
  public void undeo();
}
```

* 마지막으로 실행된 명령을 기록하는 인스턴스 변수를 추가
* Undo 실행시 기록해 뒀던 커맨드 객체 레퍼런스로 undo() 메서드 호출

```java
public class RemoteControlWithUndo {
  Command[] onCommands;
  Command[] offCommands;
  Command undoCommand;
  
  public RemoteControlWithUndo() {
    onCommands = new Command[7];
    offCommands = new Command[7];
    
    Command noCommand = new NoCommand();
    for(int i=0;i<7;i++){
      onCommands[i] = noCommand;
      offCOmmands[i] = noCommand;
    }
    undoCommand = noCommand;
  }
  
  public void setCommand(int slot, Command onCommand, Command offCommand) {
    onCommands[slot] = onCommand;
    offCommands[slot] = offCommand;
  }
  
  public void onButtonWasPushed(int slot) {
    onCommands[slot].execute();
    undoCommand = onCommands[slot];
  }
  
  public void offButtonWasPushed(int slot) {
    offCommands[slot].execute();
    undoCommand = offCommands[slot];
  }
  
  public void undoButtonWasPushed() {
    undoCommand.undo();
  }
}
```

### 여러 동작을 한 번에 처리하기

* 다른 커맨드를 실행할 수 있는 새로운 종류의 커맨드 객체를 만들어 여러 커맨드를 한번에 실행

```java
public class MacroCommand implements Command {
  Command[] commands;
  
  public MacroCommand(Command[] commands) {
    this.commands = commands;
  }
  
  public void execute() {
    for (int i=0; i<commands.length; i++) {
      commands[i].execute();
    }
  }
}
```

{% hint style="info" %}
항상 리시버가 필요한지? 커맨드 객체에서 직접 execute() 로직을 구현하면 안되나?

* 일반적으로는 리시버에 있는 행동을 호출하는 '더미' 커맨드 객체를 만든다.
* 물론 커맨드 객체에서 대부분의 행동을 처리해도 되지만
* 인보커와 리시버를 분리하기 어려워짐
* 리시버로 커맨드를 매개변수화할 수 없어짐
  {% endhint %}

{% hint style="success" %}
커맨드로 컴퓨티에이션(computation)의 한 부분(리시버와 일련의 행동)을 패키지로 묶어 일급 객체 형태로 전달할 수도 있다.

* 커맨드 객체를 생성한 뒤 오랜 시간이 지나도 그 컴퓨테이션을 호출 가능
* 심지어 다른 스레드에서도 호출 가능
* 이점을 활용해 커맨드 패턴을 활용하여 스케줄러, 스레드 풀, 작업 큐와 같은 다양한 작업에 적용함
  {% endhint %}
