Command Pattern은 행동 디자인 패턴 중 하나입니다. Command 디자인 패턴은 요청-응답 모델에서 느슨한 결합을 구현하는 데 사용됩니다.
Command Pattern
Command 패턴에서 요청은
invoker
로 보내지고, invoker는 캡슐화된 command
객체에게 전달합니다. Command 객체는 특정 동작을 수행하기 위해 요청을 적절한 Receiver
의 메서드로 전달합니다. 클라이언트 프로그램은 수신자 객체를 생성한 다음 Command에 연결합니다. 그런 다음 호출자 객체를 생성하고 명령 개체를 연결하여 동작을 수행합니다. 이제 클라이언트 프로그램이 동작을 실행하면 명령과 수신자 개체에 기반하여 처리됩니다.
Command Design Pattern 예제
실제 시나리오를 통해 커맨드 패턴을 적용하는 방법을 살펴보겠습니다. 파일 시스템 유틸리티를 제공하고자 하며, 이 유틸리티는 파일을 열고, 쓰고, 닫는 메소드를 지원해야 합니다. 이 파일 시스템 유틸리티는 Windows와 Unix와 같은 여러 운영 체제를 지원해야 합니다. 우리의 파일 시스템 유틸리티를 구현하기 위해서, 먼저 실제로 모든 작업을 수행할 리시버 클래스를 생성해야 합니다. 자바에서 인터페이스 측면에서 코딩하기 때문에, 다양한 운영 체제 버전을 위한 구현 클래스와 함께 FileSystemReceiver
인터페이스를 가질 수 있습니다.
커맨드 패턴 리시버 클래스
package com.journaldev.design.command;
public interface FileSystemReceiver {
void openFile();
void writeFile();
void closeFile();
}
FileSystemReceiver 인터페이스는 구현 클래스를 위한 계약을 정의합니다. 간단함을 위해, Unix 및 Windows 시스템과 작업할 두 가지 리시버 클래스 버전을 생성하고 있습니다.
package com.journaldev.design.command;
public class UnixFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in unix OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in unix OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in unix OS");
}
}
package com.journaldev.design.command;
public class WindowsFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in Windows OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in Windows OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in Windows OS");
}
}
Override 어노테이션을 사용한 것을 주목했고 그것이 왜 사용되었는지 궁금하다면, 자바 어노테이션과 오버라이드 어노테이션의 이점을 읽어주세요. 이제 리시버 클래스가 준비되었으니, 우리의 커맨드 클래스를 구현으로 넘어갈 수 있습니다.
명령 패턴 인터페이스 및 구현
우리는 인터페이스 또는 추상 클래스를 사용하여 기본 Command를 만들 수 있습니다. 이것은 디자인 결정이며 귀하의 요구 사항에 따라 달라집니다. 우리는 기본 구현이 없기 때문에 인터페이스를 사용합니다.
package com.journaldev.design.command;
public interface Command {
void execute();
}
이제 수신기에서 수행되는 다양한 유형의 작업에 대한 구현을 만들어야 합니다. 세 가지 작업이 있으므로 세 가지 Command 구현을 만들겠습니다. 각 Command 구현은 수신기의 적절한 메서드로 요청을 전달합니다.
package com.journaldev.design.command;
public class OpenFileCommand implements Command {
private FileSystemReceiver fileSystem;
public OpenFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
//open command는 openFile 메서드로 요청을 전달합니다.
this.fileSystem.openFile();
}
}
package com.journaldev.design.command;
public class CloseFileCommand implements Command {
private FileSystemReceiver fileSystem;
public CloseFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.closeFile();
}
}
package com.journaldev.design.command;
public class WriteFileCommand implements Command {
private FileSystemReceiver fileSystem;
public WriteFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.writeFile();
}
}
이제 수신기와 Command 구현이 준비되었으므로 호출자 클래스를 구현할 차례입니다.
명령 패턴 호출자 클래스
호출자는 명령을 캡슐화하고 명령 객체에 요청을 전달하여 처리합니다.
package com.journaldev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
우리 파일 시스템 유틸리티 구현이 준비되었으며 간단한 명령 패턴 클라이언트 프로그램을 작성할 수 있습니다. 그러나 그 전에 적절한 FileSystemReceiver
객체를 생성하는 유틸리티 메소드를 제공하겠습니다. 운영 체제 정보를 가져오기 위해 System 클래스를 사용할 수 있으므로 이를 사용하거나 입력에 따라 적절한 유형을 반환하기 위해 Factory 패턴을 사용할 수 있습니다.
package com.journaldev.design.command;
public class FileSystemReceiverUtil {
public static FileSystemReceiver getUnderlyingFileSystem(){
String osName = System.getProperty("os.name");
System.out.println("Underlying OS is:"+osName);
if(osName.contains("Windows")){
return new WindowsFileSystemReceiver();
}else{
return new UnixFileSystemReceiver();
}
}
}
이제 우리의 파일 시스템 유틸리티를 사용하는 명령 패턴 예제 클라이언트 프로그램을 만들어 봅시다.
package com.journaldev.design.command;
public class FileSystemClient {
public static void main(String[] args) {
//수신기 객체 생성
FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
//명령 생성 및 수신기와 연결
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
//호출기 생성 및 명령과 연결
FileInvoker file = new FileInvoker(openFileCommand);
//호출기 객체에서 동작 수행
file.execute();
WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
file = new FileInvoker(writeFileCommand);
file.execute();
CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
file = new FileInvoker(closeFileCommand);
file.execute();
}
}
클라이언트가 적절한 유형의 명령 객체를 생성하는 것에 주의하십시오. 예를 들어 파일을 작성하려면 CloseFileCommand
객체를 생성해서는 안됩니다. 클라이언트 프로그램은 또한 명령을 수신기에 연결하고 명령을 호출기 클래스에 연결하는 것이 책임입니다. 위의 명령 패턴 예제 프로그램의 출력은 다음과 같습니다:
Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS
명령 패턴 클래스 다이어그램
우리 파일 시스템 유틸리티 구현에 대한 클래스 다이어그램입니다.
- Command는 명령 디자인 패턴의 핵심으로, 구현을 위한 계약을 정의합니다.
- Receiver 구현은 명령 구현과 별도로 있습니다.
- Command 구현 클래스는 수신자 객체에서 호출할 메서드를 선택합니다. 각 수신자 메서드에 대해 명령 구현이 있습니다. 이는 수신자와 액션 메서드 간의 다리 역할을 합니다.
- Invoker 클래스는 클라이언트에서 명령 객체로 요청을 전달합니다.
- 클라이언트는 적절한 명령 및 수신자 구현을 인스턴스화하고 그들을 함께 연결하는 것이 책임입니다.
- 클라이언트는 또한 인보커 객체를 인스턴스화하고 명령 객체를 연결하며 액션 메서드를 실행하는 것이 책임입니다.
- 명령 디자인 패턴은 쉽게 확장 가능하며, 새로운 액션 메서드를 수신자에 추가하고 클라이언트 코드를 변경하지 않고 새로운 명령 구현을 만들 수 있습니다.
- 명령 디자인 패턴의 단점은 많은 액션 메서드와 많은 연결로 코드가 크고 혼란스러워진다는 것입니다.
Command Design Pattern JDK 예제
Runnable 인터페이스 (java.lang.Runnable) 및 Swing Action (javax.swing.Action)은 명령 패턴을 사용합니다.
Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern