웹사이트 검색

커맨드 디자인 패턴


명령 패턴은 행동 디자인 패턴 중 하나입니다. 명령 디자인 패턴은 요청-응답 모델에서 느슨한 결합을 구현하는 데 사용됩니다.

명령 패턴

명령 디자인 패턴 예제

명령 패턴을 구현할 수 있는 실제 시나리오를 살펴보겠습니다. 파일을 열고, 쓰고, 닫는 메서드가 있는 파일 시스템 유틸리티를 제공한다고 가정해 보겠습니다. 이 파일 시스템 유틸리티는 Windows 및 Unix와 같은 여러 운영 체제를 지원해야 합니다. 파일 시스템 유틸리티를 구현하려면 먼저 실제로 모든 작업을 수행할 수신기 클래스를 만들어야 합니다. 우리는 자바에서 인터페이스 측면에서 코딩하기 때문에 FileSystemReceiver 인터페이스를 가질 수 있고 Windows, Unix, Solaris 등과 같은 다양한 운영 체제에 대한 구현 클래스를 가질 수 있습니다.

명령 패턴 수신기 클래스

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");
	}

}

재정의 주석을 확인하셨습니까? 주석이 사용되는 이유가 궁금하다면 재정의 주석 이점을 읽어보세요. 이제 수신자 클래스가 준비되었으므로 Command 클래스를 구현하도록 이동할 수 있습니다.

명령 패턴 인터페이스 및 구현

인터페이스 또는 추상 클래스를 사용하여 기본 Command를 만들 수 있습니다. 이는 디자인 결정이며 요구 사항에 따라 다릅니다. 기본 구현이 없기 때문에 인터페이스를 사용할 것입니다.

package com.journaldev.design.command;

public interface Command {

	void execute();
}

이제 수신자가 수행하는 모든 다양한 유형의 작업에 대한 구현을 만들어야 합니다. 세 가지 조치가 있으므로 세 가지 명령 구현을 작성합니다. 각 명령 구현은 요청을 적절한 수신자 방법으로 전달합니다.

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 is forwarding request to openFile method
		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();
	}

}

이제 수신자 및 명령 구현이 준비되었으므로 호출자 클래스 구현으로 이동할 수 있습니다.

명령 패턴 호출자 클래스

Invoker는 명령을 캡슐화하고 명령 개체에 요청을 전달하여 처리하는 간단한 클래스입니다.

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 객체를 생성하는 유틸리티 메서드를 제공하겠습니다. 팩토리 패턴을 사용하여 입력에 따라 적절한 유형을 반환할 수 있기 때문입니다.

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) {
		//Creating the receiver object
		FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		//creating command and associating with receiver
		OpenFileCommand openFileCommand = new OpenFileCommand(fs);
		
		//Creating invoker and associating with Command
		FileInvoker file = new FileInvoker(openFileCommand);
		
		//perform action on invoker object
		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는 구현 계약을 정의하는 명령 설계 패턴의 핵심입니다.
  • 수신자 구현은 명령 구현과 별개입니다.
  • 명령 구현 클래스는 수신기 개체에서 호출할 메서드를 선택했으며 수신기의 모든 메서드에 대해 명령 구현이 있습니다. 수신자와 작업 방법 사이의 다리 역할을 합니다.
  • Invoker 클래스는 클라이언트의 요청을 명령 개체로 전달합니다.
  • 클라이언트는 적절한 명령 및 수신자 구현을 인스턴스화한 다음 함께 연결할 책임이 있습니다.
  • 클라이언트는 또한 호출자 개체를 인스턴스화하고 명령 개체를 연결하고 작업 메서드를 실행합니다.
  • 커맨드 디자인 패턴은 쉽게 확장할 수 있으며 클라이언트 코드를 변경하지 않고도 수신자에 새로운 작업 메서드를 추가하고 새로운 명령 구현을 생성할 수 있습니다.
  • 커맨드 디자인 패턴의 단점은 코드가 거대해지고 액션 메서드 수가 많고 연결이 너무 많아 혼란스러워진다는 것입니다.

커맨드 디자인 패턴 JDK 예제

Runnable 인터페이스(java.lang.Runnable) 및 Swing Action(javax.swing.Action)은 명령 패턴을 사용합니다.