[OOP] Command 패턴 정리하기

2022. 12. 9. 11:07몰랐던거/JAVA & SPRING

1. 커맨드(Command) 패턴이란?

< Command의 사전적 정의 >

커맨드의 사전적 정의는 명령이라는 의미이다.

그럼 커맨드 패턴은 어떤 패턴일까?

< Command 패턴이란? >

커맨드 패턴은 하나의 명령(기능)을 객체화한 패턴 즉, 명령을 객체로 만들어서 다루기 위한 패턴이다.

  • 이를 통해서 명령을 객체처럼 인자를 통해 전달하거나 메모리, DB 보관할 수 있게 된다.
    • 최근 배우는 JPA도 테이블을 객체처럼 다루고 싶어서 만들어진 것과 유사한 것 같다.
  • 뿐만 아니라 네트워크를 통해 다른 서버로 전달하여 해당 커맨드를 실행할수도 있다.

⇒ 이렇게 되면 명령들을 모아서 한번에 실행하는 배치 실행이나 우선순위가 높은 명령을 먼저 실행하기, 실행된 기능을 되돌리거나 다시 실행하는 Undo/Redo 등이 가능해지는 것과 같은 유연성을 발휘할 수 있다.

< Command 패턴에 대한 간단한 예시 >

UI 상에서 저장 버튼 혹은 수정 버튼을 누르면 서버로 특정 요청을 보내게 되는데 사용자 인터페이스 객체에서 이런 요청을 보내게 된다. 아래의 그림과 같을 것이다.

하지만 커맨드 패턴에서는 UI 객체들에서 직접 서버로 요청을 보내서는 안된다고 한다. 대신 아래의 그림과 같이 모든 요청을 실행시키는 별도의 객체를 만들어주는 것이다.

이렇게 된다면 UI 객체들은 어떤 요청을 보낼 것이가를 생각할 필요없이 그냥 요청을 실행시키는 커맨드 패턴 객체를 실행시키면 된다. 다른 정보를 알필요가 없다.

위 예시를 간단한 UML로 표현

해당 그림처럼 커맨드의 구현체들은 오직 자신의 기능을 실행하는 것에만 초점을 맞춘다.

그렇기에 실제로 커맨드 패턴을 이용해서 개발 시 작은 기능 구현에 집중해 개발을 하게 되고 이 기능들을 조합해서 복합적이고 더 큰 기능을 구현해나가는 느낌을 받게 된다.


2. 커맨드(Command) 패턴의 예 코드

예시 코드는 할인 쿠폰인 Voucher를 생성하고 조회하는 명령들을 Command가 구현체로 가지고 있다고 생각하자.

  • create 라는 명령을 통해서 할인 쿠폰을 자동으로 생성한다.
  • list 라는 명령을 통해서 존재하는 할인 쿠폰 리스트를 출력한다.

< Command를 실행하기 위한 부분 >

- Invoker

public class Invoker {
    private static final RunStatus runStatus = new RunStatus();
    private static final VoucherRepository repository = new VoucherRepository();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        while(runStatus.isRunnable()) {
            System.out.println("커맨드를 입력하세요 : ");
            String command = sc.nextLine();
            Command execCommand = new ExitCommand(runStatus);
            if (Objects.equals(command, "create")) {
                execCommand = new CreateCommand(repository);
            } else if (Objects.equals(command, "list")) {
                execCommand = new ListCommand(repository);
            }

            execCommand.execute();
        }
    }
}
  • 해당 클래스는 Command를 실행하기 위한 클래스이다.
  • 커맨드를 입력받게 되고 입력받은 커맨드가 create이면 CreateCommand 구현체를 이용하고 list이면 ListCommand 구현체를 이용하게 된다.
  • execute 메서드는 인자를 받지 않고 그저 실행되기만 한다.
  • 추가적으로 RunStatus는 해당 애플리케이션이 구동될지 안될지 여부를 위한 상태 클래스라고 생각해주면 좋을 것 같고 VoucherRepository는 생성된 바우처를 저장하는 저장소라고 생각하면 좋을 것 같다.

< Command와 관련된 부분 >

- Command

public interface Command {
    void execute();
}
  • Command 인터페이스에 대한 부분이고 이를 구현하여 특정 동작을 수행한다.

- ExitCommand

public class ExitCommand implements Command{

    private final RunStatus runStatus;

    public ExitCommand(RunStatus runStatus) {
        this.runStatus = runStatus;
    }

    @Override
    public void execute() {
        System.out.println("프로그램을 종료합니다.");
        runStatus.setStatus(false);
    }
}
  • 애플리케이션 종료를 위한 Command 구현체이다.

- CreateCommand

public class CreateCommand implements Command{

    private final VoucherRepository repository;

    public CreateCommand(VoucherRepository repository) {
        this.repository = repository;
    }

    @Override
    public void execute() {
        System.out.println("바우처를 생성합니다.");
        repository.insert(new Voucher(repository.generateId(), 1000));
    }
}
  • Voucher를 생성하기 위한 Command 구현체이다.
  • execute 메서드를 구현했고 실행 시 새로운 바우처를 생성해서 메모리에 추가하게 된다.
  • 여기서 궁금한 점이 생길 수 있다. 일반적으로 내가 원하는 discountAmount의 쿠폰을 생성해야 하는 것 아닌가? 그럴 경우 execute 인자로 해당 값을 넘기면 될까?
    • execute로 넘기면 안된다. execute는 그저 실행만 되면 되게 지금처럼 만들어야한다.
    • 그래서 InputValue와 같은 객체를 만들어서 입력 받을 때마다 속성을 갱신해주고 해당 객체를 Command가 만들어질때 생성자를 통해 넘기는 방식이 더 좋을 것 같다.

- ListCommand

public class ListCommand implements Command{

    private final VoucherRepository repository;

    public ListCommand(VoucherRepository repository) {
        this.repository = repository;
    }

    @Override
    public void execute() {
        System.out.println("바우처 리스트를 조회합니다.");
        repository.getVoucherList().forEach(System.out::println);
    }
}
  • 존재하는 Voucher의 리스트를 출력하기 위한 Command 구현체이다.
  • execute 실행 시 repository에 존재하는 바우처를 모두 출력한다.

< 실행결과 >

  • create 명령을 통해 바우처를 생성했고 list를 통해 그간 생성된 바우처 리스트를 조회할 수 있다.
  • 그리고 create, list가 아닌 명령어 입력시 프로그램을 종료한다.

⇒ 해당 예제 코드를 통해서 클라이언트 측에서 직접적으로 비즈니스 로직 레이어로 요청을 보내는 것이 아니라 Command라는 인터페이스를 만들고 실행 메서드를 실행하는 기능만을 수행하면 Command에서 알아서 요청을 보내고 원하는 커맨드의 기능을 수행해준다. 뿐만 아니라 해당 Command는 객체로써 관리될 수 있기에 모아서 한꺼번에 실행할수도 있고 우선순위 순으로 실행할 수 있을 것이다. 만약 객체로 관리되지 않았다면 불가능한 기능이었을 것이다.

⇒ 추가적으로 Command라는 인터페이스를 만들어줘서 클라이언트와 Command를 분리하여 DIP를 통한 OCP를 지킬 수 있게 되었음.

  • 예제코드
design-patterns/src/main/java/com/programmers/java/behavioral_patterns/command at main · twotwobread/design-patterns
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or window. Reload to refresh your session. Reload to refresh your session.
https://github.com/twotwobread/design-patterns/tree/main/src/main/java/com/programmers/java/behavioral_patterns/command


3. 커맨드 패턴의 장단점 및 다른 패턴과의 관계

< 장점 >

  • SRP. 작업 호출. 작업을 호출하는 클래스들을 수행하는 클래스들로부터 분리.
  • OCP. 기존 클라이언트 코드를 손상하지 않고 앱에 새 커맨드 도입가능.
  • 실행 취소/다시실행 과 같은 기능 구현 가능.
  • 작업들의 지연 실행이 가능 (작업들을 모아서 한꺼번에 실행, 배치 실행)
  • 간단한 커맨드들의 집합을 이용해 복잡한 커맨드를 수행가능.

< 단점 >

  • 발송자와 수신자 사이에 완전 새로운 레이어를 도입하여 코드가 더 복잡해짐.

< 다른 패턴과의 관계 >

  • 커맨드와 전략 : 되게 비슷하게 느껴짐. → 의도가 다름.
    • 커맨드 : 모든 명령을 객체로 변환
    • 전략 : 같은 작업을 수행하는 다양한 방법을 구현하기 위함. → 알고리즘의 교환.
  • 비지터 패턴은 커맨드 패턴의 강력한 버전으로 취급 가능
    • 비지터 패턴의 객체들은 다른 클래스들의 다양한 객체에 대한 작업 실행 가능.


4. Reference

커맨드 패턴
커맨드는 요청을 요청에 대한 모든 정보가 포함된 독립실행형 객체로 변환하는 행동 디자인 패턴입니다. 이 변환은 다양한 요청들이 있는 메서드들을 인수화 할 수 있도록 하며, 요청의 실행을 지연 또는 대기열에 넣을 수 있도록 하고, 또 실행 취소할 수 있는 작업을 지원할 수 있도록 합니다.
https://refactoring.guru/ko/design-patterns/command
자바로 작성된 커맨드
커맨드 는 요청 또는 간단한 작업을 객체로 변환하는 행동 디자인 패턴입니다. 이러한 변환은 명령의 지연 또는 원격 실행, 명령 기록 저장 등을 허용합니다. 복잡도: 인기도: 사용 예시들: 커맨드 패턴은 자바 코드에서 매우 일반적입니다. 대부분의 경우 작업으로 UI 요소를 매개 변수화하기 위한 콜백의 대안으로 사용되며 작업 대기, 작업 기록 추적 등에도 사용됩니다.
https://refactoring.guru/ko/design-patterns/command/java/example
GoF의 Design Pattern - 20. Command
안녕하세요, GIS Developer입니다. 이 영상은 GoF의 디자인패턴 중 Command 패턴에 대한 내용입니다. 커맨드는 명령이라는 의미입니다. 커맨드 패턴은 이 명령을 객체로 다루기 위한 패턴이며 명령은 어떤 기능을 의미합니다. 즉 기능을 객체화 시킨다는 말인데요. 이렇게 명령 즉 기능을 객체화 시키게 되면 매서드 인자를 통해 전달할 수도 있고 메모리에 보관할 수 있습니다.
https://www.youtube.com/watch?v=sYIB1VrN1ik

Uploaded by N2T