컴퓨터과학/0 + 소프트웨어 아키텍처(디자인 패턴)

[소프트웨어 아키텍처] 3. 커맨드 패턴(Command Pattern-java)

힘들면힘을내는쿼카 2022. 10. 20. 16:04
728x90
반응형

커맨드 패턴

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

리시버의 메서드 호출을 캡슐화 한다.
메서드 호출을 캡슐화하면 계산 과정의 부분을 결정화할 수 있기에 계산하는 코드를 호출한 객체는 그 일이 어떤 방법으로 처리되는지 신경 쓸 필요가 없다.

쉽게 이야기하면 다음과 같습니다.

Command 를 통해서 리시버의 특정 메서드를 캡슐화 하고,

인보커(호출자)는 리시버에 관계 없이(Command에서 캡슐화했기 때문) Command 객체의 메서드를 호출하면 됩니다.

 

즉, 커맨드 패턴은 호출을 캡슐화 합니다.

 

 

위 내용을 레스토랑의 종업원, 주방장의 예시로
코드를 사용해서 표현하면 다음과 같다.

 

Command

public interface Command {
    void execute();
}

OrderCommand

public class OrderCommand implements Command {

    private Chef chef;

    public OrderCommand(Chef chef) {
        this.chef = chef;
    }

    @Override
    public void execute() {
        chef.checkCookList();
        chef.makeCook();
    }
}

Employee

인보커 - 명령이 들어있으며 execute()를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요구

public class Employee {
    private Command command;
    private List<String> list;

    public void takeOrder(List<String> list) {
        this.list = list;
        System.out.println("종업원이 손님에게 주문을 받고 있습니다.");

        for (String s : list) {
            System.out.print(s+" ");
        }
        System.out.println("주문 받았습니다.");
    }

    public List<String> getList() {
        return list;
    }

    public void setCommand(Command command) {
        this.command = command;
    }

    public void orderUp() {
        System.out.println("종업원이 주방장에게 주문서를 전달 합니다.");
        command.execute();
    }
}

Chef

리시버 - 요구사항을 수행할 때 어떤 일을 처리해야 하는지 알고 있는 객체

public class Chef {

    List<String> list = new ArrayList<>();

    public void setCookList(List<String> list) {
        this.list = list;
    }

    public void checkCookList() {
        setCookList(this.list);
        System.out.println("주방장이 주문서를 확인했습니다.");
    }

    public void makeCook() {
        System.out.println("주방장이 요리를 만들고 있습니다.");
        for (String cookList : list) {
            System.out.print(cookList+" ");
        }
        System.out.println("요리를 완성했습니다.");
    }
}

Restaurant

public class Restaurant {
    public static void main(String[] args) {

        List<String> cookList = List.of("햄버거", "치킨", "파이"); //주문서

        Employee employee = new Employee();
        employee.takeOrder(cookList); //종업원이 주문서 수령

        Chef chef = new Chef();
        chef.setCookList(cookList);

        Command orderCommand = new OrderCommand(chef); //주방장이 실행할 커맨드 객체 생성

        employee.setCommand(orderCommand);
        employee.orderUp(); //주방장에게 음식주문 요청
    }
}

결과

종업원이 손님에게 주문을 받고 있습니다.
햄버거 치킨 파이 주문 받았습니다.
종업원이 주방장에게 주문서 내용을 알려줍니다.

주방장이 주문서를 확인했습니다.
주방장이 요리를 만들고 있습니다.
햄버거 치킨 파이 요리를 완성했습니다.

 

커맨드 패턴 구조

  • Client
      * `ConcreteCommand`를 생성하고 `Receiver`를 설정합니다.
  • Invoker
    • 인보커에는 명령이 들어 있으며, execute() 메서드를 호출함으로써 커맨드 객체에게 특정 작업을 수행해 달라는 요구를 합니다.
  • Receiver
    • 리시버는 요구사항을 수행할 때 어떤 일을 처리해야 하는지 알고있는 객체입니다.
  • Command
    • 모든 커맨드 객체에서 구현해야 하는 인터페이스
    • 모든 명령은 execute() 메서드 호출로 수행되며, 이 메서드는 리시버에 특정 작업을 처리하라는 지시를 전달 합니다.
    • ConcreteCommand
      • Command의 구현체로 특정 행동과 리시버를 연결해줍니다.
      • 인보커에서 execute()를 호출하면 ConcreteCommand 객체에서 리시버에 있는 메서드를 호출해서 해당 작업을 처리합니다.

 

리모컨 애플리케이션 개발

커맨드 패턴을 사용해서 TV, 에어컨을 컨트롤 할 수 있는 리모컨 애플리케이션을 개발해보자!
여기에서 간단하게 undo()메서드는 off()메서드를 캡슐화 하였다.

Device

public interface Device {
    void on();
    void off();
}

Aircon

public class Aircon implements Device {

    private String name;

    public Aircon(String name) {
        this.name = name;
    }

    @Override
    public void on() {
        System.out.println(name+"를 켭니다.");
    }

    @Override
    public void off() {
        System.out.println(name+"를 끕니다.");
    }
}

TV

public class TV implements Device {

    private String name;

    public TV(String name) {
        this.name = name;
    }

    @Override
    public void on() {
        System.out.println(name+"를 켭니다.");
    }

    @Override
    public void off() {
        System.out.println(name+"를 끕니다.");
    }
}

Command

public interface Command {
    void execute();
    void undo();
}

TvCommand

public class TvCommand implements Command {

    private Device device;

    public TvCommand(Device device) {
        this.device = device;
    }
    //캡슐화
    @Override
    public void execute() {
        device.on();
    }
    //캡슐화
    @Override
    public void undo() {
        device.off();
    }
}

AirconCommand

    private Device device;

    public AirconCommand(Device device) {
        this.device = device;
    }
      //캡슐화
    @Override
    public void execute() {
        device.on();
    }
      //캡슐화
    @Override
    public void undo() {
        device.off();
    }
}

RemoteController

public class RemoteController {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void onButtonWasPush() {
        command.execute();
    }

    public void offButtonWasPush() {
        command.undo();
    }
}

Client

public class Client {
    public static void main(String[] args) {
        //Receiver 생성
        Device tv = new TV("티비");
        Device aircon = new Aircon("에어컨");
        //Command 생성
        Command airconCommand = new AirconCommand(aircon);
        Command tvCommand = new TvCommand(tv);
        //Invoker 생성
        RemoteController remoteController = new RemoteController();
        //커맨드 의존성 추가
        remoteController.setCommand(airconCommand);
        remoteController.onButtonWasPush();
        remoteController.offButtonWasPush();
        //커맨드 의존성 추가
        remoteController.setCommand(tvCommand);
        remoteController.onButtonWasPush();
        remoteController.offButtonWasPush();
    }
}

결과

에어컨를 켭니다.
에어컨를 끕니다.
티비를 켭니다.
티비를 끕니다.

 

정리

  • 커맨드 패턴을 사용하면 요청하는 객체와 요청을 수행하는 객체를 분리할 수 있다.
  • 분리하는 과정의 중심에는 커맨드 객체가 있다.
  • 커맨드 객체가 행동이 들어있는 리시버를 캡슐화 한다.
  • 인보커는 커맨드 객체의 execute()메서드를 호출한다.
  • execute()메서드가 마지막으로 호출되기 전의 상태로 되돌리는 작업 취소 메서드를 구현하면 커맨드 패턴으로 작업 취소 기능을 구현할 수 도 있다.
  • 매크로 커맨드는 커맨드를 확장해서 여러 개의 커맨드를 한 번에 호출할 수 있게 해주는 가장 간편한 방법이다.
  • 스마트 커맨드 객체를 사용하면 요청을 스스로 처리한다.
  • 커맨드 패턴을 활용해서 로그 및 트랜잭션 시스템을 구현할 수 있다.

 

 

 

728x90
반응형