[Design Pattern] Command Pattern

Loopy·2022년 3월 21일
0

디자인패턴

목록 보기
5/9
post-thumbnail

☁️ 커맨드 패턴이란?

서로 다른 요구사항(요청, 명령)들을 객체로 캡슐화 시키는 패턴으로, 실행과 요청을 분리해서 유연성을 높이는 방식이다.

  1. 캡슐화 시키면, 복잡성이 감춰지므로 사용하는 입장에서는 구체적인 내용을 몰라도 된다.
  2. 새로운 요청이 들어와도, 중앙에서 명령하는 객체는 추상화된 Command 인터페이스만 알고 있으니 코드에 변경이 가해지지 않는다.

즉 겉으로 보기에는 동일한 명령을 내리는 것처럼 보이지만, 실질적으로 하는 일은 다른 것이다. 커맨트 패턴은 아래와 같은 인터페이스를 가지고 있으며, 모든 요청 객체는 해당 인터페이스를 구현한다.

public interface Command {    // 추상화
	public void execute();
}

핵심은 추상화를 통한 실행 책임 분리!

☁️ 커맨트 패턴 예시

리모컨 예시를 통해 위 그림을 설명해보자. 간단하게, 여러 작업을 수행해야 하는 리모컨을 구현해야 하는 상황이다.

public interface Command {
    public void execute();  // 요청 실행
    public void undo();     // 요청 취소
}
  1. ConcreteCommand
    구체적인 요청 정보들이 담겨 있는 클래스이다. 실제 행동과 그 행동을 수행하는 주체를(리시버)를 한 객체에 집어넣고, execute() 라는 메소드 하나만 외부에 공개한다.
public class LightOnCommand implements Command {
    Light light;   

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }

    public void undo() {
        light.off();
    }
}
  1. Receiver
    실제 요청/명령을 수행하는 주체이다. 위에서 Light 객체를 의미한다.
  1. Invoker
    최초로 요청을 받은 이후, 요청을 저장해둔 Command으로 실행을 넘긴다. 다시 말하지만 Command 인터페이스만 알고 있고 Command 가 실제 어떻게 실행되는지 내부는 모르는 상태이다.
public class RemoteControl {  // 리모컨 객체
    Command[] onCommands;    // Command 객체들을 저장해둔다.
    Command[] offCommands;   // 여러 종류의 요청들이 저장되어야 하니 배열로 생성한다.
    Command undoCommand;
  
    public RemoteControl() {
       // 필드 배열 초기화 코드
    }

    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() {         //undo 기능 추가
      undoCommand.undo();
    }
 }

이제 실제로 클라이언트 쪽 리모컨을 사용하는 코드를 보자.

  1. 커맨드 객체(요청)을 생성한다.
  2. 리모컨에 해당 요청을 담는다.
  3. 리모컨 버튼 메서드를 실행시킨다.

너무 간단해졌지 않는가? Command 인터페이스로 추상화를 시키지 않았다면 얼마나 리모컨 클래스가 복잡해졌을 지 상상도 안간다.

public class RemoteLoader {
  public static void main(String[] args) {
      RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
      Light livingRoomLight = new Light("Living Room");
      LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
      LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
      remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);

      remoteControl.onButtonWasPushed(0);
      remoteControl.offButtonWasPushed(0);
      remoteControl.undoButtonWasPushed();
  }
}

매크로 커맨드

만약 여러개의 커맨드를 가지고 있는 커맨드를 구현(여러개의 Receiver 처리)하고 싶다면, 아래와 같은 클래스만 생성해주면 된다.

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();
        }
    }
}

☁️ 커맨드 패턴의 활용

요청 로그 기록에 사용할 수 있다.

interface Command {
	execute();
    store();   // 요청이 수행되면 실행 히스토리를 기록한다.
    load();    // 시스템 다운 시 최근 수행된 작업부터 다시 진행한다.
}

또한 위와 같은 기능을 활용해서 트랜잭션 시스템을 구현할 수 있다. 트랜잭션에서는 모든 작업이 완벽하게 처리되도록 하거나, 문제가 발생했다면 아무것도 처리되지 않게 롤백되어야 하기 때문이다.

profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글