Link
Today
Total
06-28 01:07
Archives
관리 메뉴

초보개발자 긍.응.성

[디자인 패턴] 6. 커맨드 패턴(Command Pattern) 본문

설계패턴(Design Pattern)

[디자인 패턴] 6. 커맨드 패턴(Command Pattern)

긍.응.성 2024. 6. 16. 14:36
반응형

이 글은 헤드퍼스트 디자인패턴과 GoF 디자인패턴을 읽고 정리한 글입니다.

1. 커맨드 패턴(Command Pattern)

커맨드 패턴은 요청 내역을 객체(Command)로 캡슐화하여 객체를 서로 다른 요청 내역에 따라 매개변수화 할 수 있도록 합니다. 요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있습니다.

1.1. 구성 요소

Command

  • 작업 수행을 위한 인터페이스입니다.
  • 모든 커맨드 구상 클래스는 이 인터페이스를 구현합니다.

ConcreteCommand

  • 특정 작업과 Recevier를 연결해줍니다.
  • execute() 호출 시 Receiver 객체의 메서드를 호출하여 작업을 처리합니다.

Client

  • ConcreteCommand를 생성하고 Receiver를 설정합니다.

Invoker

  • 커맨드 객체를 갖습니다.
  • 커맨드 객체의 execute() 메서드를 호출함으로써 특정 요청의 수행을 커맨드 객체에게 요구합니다.

Receiver

  • 작업 요청 시 실제 일을 처리할 객체입니다.

1.2. 적용 방법

  1. Command 인터페이스를 선언합니다.
  2. 요청을 ConcreteCommand 로 추출합니다. 각 ConcreteCommand 에는 실제 작업을 처리할 Receiver 객체와 요청 인자를 저장할 필드를 갖습니다.
  3. Invoker 역할을 할 클래스를 식별합니다. 여러 Recevier들을 사용하여 직접 요청을 처리하고 있다면 Invoker 일 수 있습니다. 이 Invoker 에 대하여 Command 들을 저장할 필드를 선언합니다.
  4. Invoker에서 Receiver 에게 직접 요청을 보내는 코드를 Command 를 통해 처리하는 방식으로 수정합니다.
  5. Client 는 Receiver와 ConcreteCommand를 만들고 이를 Invoker에 연결합니다.

1.3. 적용 시기

  • 수행할 작업을 매개변수화 하려고 할 때 사용할 수 있습니다.
  • 요청에 대하여 큐잉하거나 특정 시점에 실행하고 싶을 때 사용할 수 있습니다. Command 객체로 분리함으로 써 요청과는 독립된 라이프타임을 가질 수 있기 때문입니다.
  • 작업 취소 기능이나 변경 사항에 대한 로깅을 위해 사용할 수 있습니다. Command 에 대한 로깅은 시스템의 장애 복구시에도 로깅된 이력에 따라 Command 를 재수행 하는 식으로 사용될 수 있습니다.

1.4. 정리

커맨드 패턴은 통해 작업을 요청하는 Invoker 와 작업을 수행하는 Receiver 간의 의존성을 Command 객체를 통해 분리합니다. 이를 통해 단일 책임 원칙을 만족합니다. 또한 기존 클라이언트 코드의 수정없이 새로운 커맨드를 추가할 수 있다는 점에서 개방/폐쇄 원칙도 만족합니다. 필요에 따라 간단한 Command들을 조합하여 하나의 Command에서 작업을 처리하도록 구현 가능합니다.

커맨드 패턴의 모든 요청과 작업은 Command 를 거쳐서 처리됩니다. Invoker 가 직접 Recevier를 갖고 직접 요청을 처리하는 구조에서, Command 라는 일급 객체 생성하여 책임을 분산시키고 subclassing을 통해 쉽게 기능을 확장할 수 있도록 합니다.

요청하는 객체와 요청을 수행하는 객체를 분리하고 싶을 때 커맨드 패턴의 사용을 고려해보면 좋을것 같습니다.

2. 커맨드 패턴 구현 방법 및 고려 사항

2.1. How intelligent should a command be?

Command 객체를 구현 시 Command의 책임을 어느 수준으로 줄것인지 고려해야 합니다. 먼저 가장 책임을 가지지 않는 경우 Command 는 자체적으로 처리하는 작업은 없으며, Recevier에게 모든 요청 수행을 위임합니다. 이러한 경우 Command는 Recevier에 대한 참조만을 갖습니다. 반대로, 가장 책임을 많이 가지는 경우 Recevier 없이 모든 작업을 직접 구현으로 갖고있습니다. 이러한 구현은 해당 Command에 독립적인 처리 로직을 넣고 싶거나, 위임할 적절한 Recevier 가 없을 때 사용하기 좋습니다.

2.2. Supporting undo and redo - 실행 취소 및 재실행 지원

Command 가 execute() 와 함께 reverseExecute()(undo())를 제공한다면 undo(실행 취소) 및 redo(재실행) 기능을 지원할 수 있습니다. 이를 위해서 ConcreteCommand 클래스는 다음과 같은 상태값들을 가져야 합니다.

  • Recevier 객체
  • Recevier에서 작업 수행 시 사용된 인자
  • 작업 수행 이전 Recevier의 상태값(Recevier는 이전 상태로 원복하기 위한 메서드를 제공해야 합니다)

단 한번의 undo를 위해서는 마지막 Command 만 저장하면 되지만 여러번의 undo를 지원하기 위해서는 큐와 같은 작업 대기열에 Command의 이력을 저장해야합니다.

또한 undo가 불가능한 Command가 존재한다면 이력과 함께 Command의 복사본을 저장해두어야 합니다. Command의 복사본이란 Command가 가진 상태 값들과 참조하는 Recevier의 상태 값들도 모두 포함됩니다.

2.3. Avoiding error accumulation in the undo process - 실행 취소 시 오류를 방지하라

Hysteresis 상황에서는 이전 작업의 과정들에 따라 작업 결과가 달라지기에 undo, redo 작업이 정상적으로 동작하지 않을 수 있습니다. 그렇기에 undo, redo 작업을 보장하기 위해 Command 는 더 많은 정보들을 저장하고 있어야 합니다. 메멘토 패턴을 함께 사용한다면 Command 가 작업 수행 별로 스냅샷을 만들어 관리하도록 구현할 수 있습니다.

3. 참조링크

반응형
Comments