개발 기록
> [GOF Design Pattern] 행동 패턴 : 커맨드 (Command) 패턴 - 개념과 구현 본문

* 행동 패턴 : 클래스와 객체들이 상호작용하는 방법과 역할을 분담하는 방법을 다루는 패턴
■
1. Command (명령) 패턴 개념
(1) 개념
- 하나의 추상 클래스에 메서드를 하나 만들고 각 명령이 들어오면 그에 맞는 서브 클래스가 선택되어 실행하는 것
- 주어진 작업(명령)을 수행하는 데 필요한 모든 데이터(호출 메소드, 메소드 인수 등)를 객체에 캡슐화
예를 들어 주문에 따라 요리를 하는 기능을 구현한다고 했을 때 Command 패턴을 적용시키지 않고 하나의 객체로 명령을 실행하게 된다면, 주문(매개변수)들이 변경될 때마다 분기처리를 해줘야 될것이다. 추가/수정 사항이 생기면 기존 코드를 변경해야 된다. 이는 OCP (개방 폐쇄 원칙) 를 위반하게 된다. 만약 이처럼 하나의 기능을 공통적으로 사용하게 될 시에 Command 패턴을 고려한 후 설계하는 것이 좋다.
* ocp(개방 폐쇄 원칙): 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙
☞ 장점
① 간단한 커맨드들의 집합을 복잡한 커맨드로 조합할 수 있다.
② 디커플링: 작업 요청자와 수신자를 분리함으로써 시스템의 한 부분을 변경해도 다른 부분을 수정할 필요가 없다.
③ 실행 취소 메커니즘: Command 개체의 기록 유지함으로 실행 취소 및 다시 실행 기능을 구현하는 데 적합하다.
④ 확장성: 기존 클라이언트 코드를 변경하지 않고도 새로운 명령을 추가할 수 있다.
☞ 단점
① 백업기능 과도한 RAM 소모 가능성.
② 발송자와 수신자 사이에 완전히 새로운 레이어를 도입하기 때문에, 명령에 대한 기능이 추가 될때마다 코드가 더 복잡해질 수 있다.
■
2. Command (명령) 패턴 구조
(1) 구조
* 기존에 직접적으로 서로를 호출하던 클래스들 사이에서 커맨드 패턴을 적용하면, 커맨드 객체를 통해 호출하도록 변경됩니다. 이로 인해 호출자 클래스가 수신자 클래스에 대해 직접적인 의존성을 갖지 않게 되며, 커맨드 클래스가 호출자와 수신자를 연결해준다. 호출자 클래스가 수신자 클래스의 세부 구현에 의존하지 않게 된다.
(2) 구성요소
▶ 1. Command(명령)
: 특정 작업을 실행하기 위한 명령 인터페이스이다. 일반적 수행할 작업을 캡슐화하는 execute() 와 같은 단일 메서드가 포함된다.
▶ 2. ConcreteCommand
: Command 인터페이스 구현체.Receiver 객체를 가지고 있으며, 수신자의 메서드를 호출한다.
▶ 3. Receiver(수신자)
: 명령의 실제 작업을 수행한다. 여기에는 작업을 수행하는 데 필요한 로직이 포함되어 있다.
▶ 4. Invoker(호출자)
: 명령 요청자. execute() 메서드 호출로 명령 실행을 트리거한다. 한 Invoker 객체에 다수의 Command 객체가 전달될 수 있다.
: 호출자는 주어진 명령을 실행하는 방법을 알고 있지만 명령이 어떻게 구현되었는지는 모른다.
▶ 5. Client
: Command 개체를 생성하고 구성하며 Invoker 에 Command 객체를 전달함으로써 어느 시점에에서 어떤 명령을 수행할지를 결정한다.
■
3. Command (명령) 패턴 구현
▶Command interface
: TextFileOperation 인터페이스는 execute() 메서드를 정의하고 OpenTextFileOperation, SaveTextFileOperation class 들이 구현하여 구체적인 작업을 한다.
@FunctionalInterface
public interface TextFileOperation {
String execute();
}
▶ ConcreteCommand
: TextFileOperation command 는 수신자 개체, 호출할 메서드, 인수를 포함하여 텍스트 파일을 열고 저장하는 데 필요한 모든 정보를 캡슐화
// 파일 열기
public class OpenTextFileOperation implements TextFileOperation {
// receiver Object
private TextFile textFile;
// constructors
public OpenTextFileOperation(TextFile textFile) {
this.textFile = textFile;
}
@Override
public String execute() {
return textFile.open();
}
}
// 파일 저장
public class SaveTextFileOperation implements TextFileOperation {
// receiver object
private TextFile textFile;
// constructors
public SaveTextFileOperation(TextFile textFile) {
this.textFile = textFile;
}
@Override
public String execute() {
return textFile.save();
}
}
▶ The Receiver Class
작업을 수행 객체로 Command 의 Execute() 메서드가 호출될 때 실제 작업을 수행하는 구성 요소이다.
public class TextFile {
private String name;
// constructor
public String open() {
return "Opening file " + name;
}
public String save() {
return "Saving file " + name;
}
}
▶ The Invoker Class
TextFileOperationExecutor 클래스 는 Client 로부터 Command 개체를 분리하고 TextFileOperation Command 개체내에 캡슐화된 메서드를 호출한다.
* 호출자가 명령을 실행하는 것 외에 명령을 저장하고 대기열에 추가하기도 하는데 매크로 기록이나 실행 취소 및 다시 실행 기능과 같은 일부 추가 기능을 구현할 때는 사용한다.
public class TextFileOperationExecutor {
// List에 Command 저장(추가 제어 하는 경우 사용)
private final List<TextFileOperation> textFileOperations
= new ArrayList<>();
// setCommand 로도 받을 수도 있다.
// public void setCommand(Command command) {
// this.command = command;
// }
public String executeOperation(TextFileOperation textFileOperation) {
textFileOperations.add(textFileOperation);
// command 개체를 호출하여 Execute() 메서드 실행
return textFileOperation.execute();
}
}
▶ The Client Class
클라이언트는 실행할 명령과 실행 프로세스를 제어한다. 테스트라서 main 메서드를 사용하여 클라이언트 클래스를 만들었다.
public static void main(String[] args) {
TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor();
TextFileOperation command1 = new OpenTextFileOperation(new TextFile("file1.txt");
TextFileOperation command2 = new SaveTextFileOperation(new TextFile("file2.txt");
textFileOperationExecutor.executeOperation(command1);
textFileOperationExecutor.executeOperation(command2);
}
// 람다
TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt");
textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt");
// 메소드 참조
TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);
커맨드를 사용하여 모든 작업을 객체로 변환할 수 있다. 작업의 매개변수들은 해당 객체의 필드들이 된다. 이 변환은 작업의 실행을 연기하고, 해당 작업을 대기열에 넣고, 커맨드들의 기록을 저장한 후 해당 커맨드들을 원격 서비스에 보내는 등의 작업을 가능하게 한다.
* DispatcherServlet은 Front Controller 패턴을 이용해서 구현되었는데 Command 패턴을 이용하여 Front Controller 패턴을 구현할 수 있다.
* Java의 Thread가 전형적인 Command 패턴의 형태라 할 수 있다.
■
4. Command Pattern 사용 예
(1) Commad Pattern 은 언제 사용할까?
☞ 명령 대기열: 요청을 큐에 저장하는 방식으로 작업큐나 스케줄러와 같은 작업에 적용 가능
☞ 매크로 명령: 여러 명령이 단일 명령으로 그룹화되는 강력한 개념인 매크로 명령을 구현
☞ 작업 내용을 로그로 기록하여 프로그램 실행 도중 에러 발생 시 롤백하는 기능에 적용 (실행된 명령 스택 유지)
☞ 명령어 실행 저장이 가능하므로 작업이 요청된 시점과 수행되는 시점을 분리하고자 할 때 응용 가능
☞ 작업들 객체을 매개변수화하려는 경우 커맨드 패턴 사용
☞ command 개체의 직렬화/역직렬화
: 명령 기록 저장 및 복원과 같은 기능을 활성화한다. 커맨드의 실행을 지연하고 예약할 수 있다.
: 네트워크를 통해 커맨드를 대기열에 추가하거나 로그(기록) 하거나 전송할 수 있다.
☞ command 패턴을 확장하여 비동기 명령/ 동적 명령 (동적으로 command 인스턴스화)을 처리한다.
(2) Commad Pattern 사용 사례
① GUI 애플리케이션 (ex. 버튼 클릭, 메뉴 선택, 키보드 단축키 등)
② 텍스트 편집기 (작성, 삭제, 실행 취소/다시 실행 등)
③ 원격 제어 (ex. TV 리모컨, 홈 자동화 시스템)
④ 작업 스케줄링
: 작업 스케줄러는 명령 개체의 대기열을 유지 관리하고 우선 순위, 시간, 종속성과 같은 미리 정의된 기준에 따라 개체를 실행한다.
⑤ 트랜잭션 관리(CRUD, 커밋, 롤백)
■
5. 다른 패턴과의 관계
(1) 차이점
ⓛ 커맨드와 전략패턴은 둘 다 어떤 작업으로 객체를 매개변수화하는 데 사용할 수 있는데서 비슷해 보이지만 의도가 다르다.
커맨드는 모든 작업을 객체로 변환하여 매개변수에 따라 다른 작업을 실행하지만 전략 패턴은 일반적으로 같은 작업을 수행하는 다양한 방법을 설명한다.(ex. 알고리즘 교환)
② 커맨드, 중재자, 옵저버 및 책임 연쇄 패턴은 공통적으로 요청의 발신자와 수신자를 연결하는 다양한 방법을 다루지만 차이가 있다.
| 발신자와 수진자 간의 연결 | |
| 커맨드 | 단방향 연결 |
| 중재자 | 중재자 객체를 통한 간접 통신 강제 |
| 옵서버 | 수신자가 요청 수신을 동적으로 구독 및 구독 취소 가능 |
| 책임 연쇄 패턴 | 수신자 중 하나에 의해 요청이 처리될 때까지 요청을 순차적으로 전달 |
(2) WITH
① 책임 연쇄 패턴 + 커맨드 : 책임 연쇄 패턴 핸들러를 커맨드로 구현 할 수 있다.
② 커맨드 + 메멘토 : '실행 취소'를 구현 할 때 함께 사용 할 수 있다.
③ 커맨드 + 프로토타입 : 커맨드 패턴의 복사본들을 기록에 저장해야 할 때 활용할 수 있다.
④ 비지터 + 커맨드 : 비지터 패턴은 커맨드 패턴의 윗 버전으로 볼 수 있다. 비지터 패턴의 객체들은 다른 클래스들의 다양한 객체에 대한 작업을 실행할 수 있다.
참고
https://johngrib.github.io/wiki/pattern/command/
'디자인 패턴' 카테고리의 다른 글
| > [GOF Design Pattern] 행동 패턴 : 옵저버 (Observer) 패턴 - 개념과 구현 (0) | 2024.05.17 |
|---|---|
| > [GOF Design Pattern] 생성 패턴 : 팩토리 메서드(Factory Method) 패턴 - 개념과 구현 (0) | 2024.03.09 |
| > [GOF Design Pattern] 행동 패턴 : 전략 (Strategy) 패턴 - 개념과 구현 (6) | 2024.02.05 |
| > [GOF Design Pattern] 구조 패턴 : 프록시(Proxy) 패턴 - 개념과 구현 (0) | 2024.01.17 |
| > 디자인 패턴에 대해 알아보기 - 개념과 유형 (0) | 2024.01.17 |