1. 개요

명령 패턴은 동작 디자인 패턴이며 GoF 의 공식 디자인 패턴 List의 일부입니다. 간단히 말해서 패턴은 호출할 메서드, 메서드의 인수 및 메서드가 속한 개체를 포함하여  지정된 작업(명령)을 수행하는 데 필요한 모든 데이터를 개체에 캡슐화 하려고 합니다.

이 모델을 사용하면 소비자로부터 명령을 생성하는 개체를 분리 할 수 있으므로 패턴이 일반적으로 생산자-소비자 패턴으로 알려져 있습니다.

이 사용방법(예제)에서는 개체 지향 및 개체 기능 접근 방식을 모두 사용하여 Java에서 명령 패턴을 구현하는 방법을 배우고 어떤 사용 사례에서 유용할 수 있는지 살펴보겠습니다.

2. 객체 지향 구현

클래식 구현에서 명령 패턴은 명령, 수신자, 호출자 및 클라이언트의 네 가지 구성 요소 를 구현해야 합니다 .

패턴이 작동하는 방식과 각 구성 요소가 수행하는 역할을 이해하기 위해 기본 예제를 만들어 보겠습니다.

텍스트 파일 애플리케이션을 개발한다고 가정해 봅시다. 이 경우 텍스트 파일 열기, 쓰기, 저장 등과 같은 일부 텍스트 파일 관련 작업을 수행하는 데 필요한 모든 기능을 구현해야 합니다.

따라서 애플리케이션을 위에서 언급한 네 가지 구성 요소로 분류해야 합니다.

2.1. 명령 클래스

명령은 호출할 메서드, 메서드 인수 및 메서드를 구현하는 개체(수신자라고 함)를 포함하여 작업을 실행하는 데 필요한 모든 정보를 저장 하는 역할을 하는 개체입니다.

명령 개체가 작동하는 방식을 보다 정확하게 이해하기 위해 단일 인터페이스와 두 가지 구현을 포함하는 간단한 명령 계층 개발을 시작하겠습니다.

@FunctionalInterface
public interface TextFileOperation {
    String execute();
}
public class OpenTextFileOperation implements TextFileOperation {

    private TextFile textFile;
    
    // constructors
    
    @Override
    public String execute() {
        return textFile.open();
    }
}
public class SaveTextFileOperation implements TextFileOperation {
    
    // same field and constructor as above
        
    @Override
    public String execute() {
        return textFile.save();
    }
}

이 경우 TextFileOperation 인터페이스는 명령 개체의 API를 정의하고 OpenTextFileOperationSaveTextFileOperation 의 두 가지 구현 이 구체적인 작업을 수행합니다. 전자는 텍스트 파일을 여는 반면 후자는 텍스트 파일을 저장합니다.

명령 개체의 기능을 보는 것은 분명합니다. TextFileOperation 명령 은 수신자 개체, 호출할 메서드 및 인수를 포함하여 텍스트 파일을 열고 저장하는 데 필요한 모든 정보를 캡슐화합니다 . 그러나 그들은 될 수 있습니다).

파일 작업을 수행하는 구성 요소가 수신기( TextFile 인스턴스) 라는 점을 강조할 가치가 있습니다.

2.2. 리시버 클래스

리시버는 일련의 응집력 있는 작업을 수행 하는 개체입니다 . 명령의 execute() 메서드가 호출될 때 실제 작업을 수행하는 구성 요소입니다.

이 경우 TextFile 개체 를 모델링하는 역할을 하는 수신자 클래스를 정의해야 합니다.

public class TextFile {
    
    private String name;
    
    // constructor
    
    public String open() {
        return "Opening file " + name;
    }
    
    public String save() {  
        return "Saving file " + name;
    }
    
    // additional text file methods (editing, writing, copying, pasting)
}

2.3. 호출자 클래스

호출자는 주어진 명령을 실행하는 방법을 알고 있지만 명령이 어떻게 구현되었는지는 모르는 객체입니다. 명령의 인터페이스만 알고 있습니다.

경우에 따라 호출자는 명령을 실행하는 것 외에도 명령을 저장하고 Queue에 넣습니다. 이는 매크로 기록 또는 실행 취소 및 다시 실행 기능과 같은 일부 추가 기능을 구현하는 데 유용합니다.

이 예제에서는 명령 개체를 호출하고 명령의 execute() 메서드 를 통해 실행하는 추가 구성 요소가 있어야 한다는 것이 분명해졌습니다 . 이것이 바로 호출자 클래스가 작동하는 곳 입니다.

호출자의 기본 구현을 살펴보겠습니다.

public class TextFileOperationExecutor {
    
    private final List<TextFileOperation> textFileOperations
     = new ArrayList<>();
    
    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

TextFileOperationExecutor 클래스는 소비자로부터 명령 개체를 분리하고 TextFileOperation 명령 개체 내에 캡슐화된 메서드를 호출하는 얇은 추상화 계층 입니다  .

이 경우 클래스는 명령 개체도 List 에 저장합니다 . 물론 작업 실행 프로세스에 추가 제어를 추가해야 하는 경우가 아니면 패턴 구현에서 필수 사항은 아닙니다.

2.4. 클라이언트 클래스

 클라이언트는 실행할 명령과 실행할 프로세스의 단계를 지정 하여 명령 실행 프로세스를 제어 하는 ​​개체입니다 .

따라서 패턴의 형식적 정의를 정통으로 사용하려면 일반적인 main 메서드 를 사용하여 클라이언트 클래스를 만들어야 합니다 .

public static void main(String[] args) {
    TextFileOperationExecutor textFileOperationExecutor
      = new TextFileOperationExecutor();
    textFileOperationExecutor.executeOperation(
      new OpenTextFileOperation(new TextFile("file1.txt"))));
    textFileOperationExecutor.executeOperation(
      new SaveTextFileOperation(new TextFile("file2.txt"))));
}

3. 객체 기능 구현

지금까지 명령 패턴을 구현하기 위해 개체 지향 접근 방식을 사용했는데 모두 훌륭했습니다.

Java 8부터는 람다 식 및 메서드 참조를 기반으로 하는 개체 기능 접근 방식을 사용하여 코드를 좀 더 간결하고 덜 장황하게 만들 수 있습니다.

3.1. 람다 식 사용

TextFileOperation 인터페이스는 기능적 인터페이스 이므로 TextFileOperation 인스턴스를 명시적으로 만들지 않고도 람다 식 형식의 명령 객체를 호출자에게 전달할 수 있습니다 .

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt");
textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt");

상용구 코드의 양을 줄였기 때문에 이제 구현이 훨씬 더 간소화되고 간결해 보입니다 .

그럼에도 불구하고 질문은 여전히 ​​유효합니다. 이 접근 방식이 객체 지향 접근 방식에 비해 더 나은가요?

음, 까다롭습니다. 더 간결한 코드가 대부분의 경우 더 나은 코드를 의미한다고 가정한다면 실제로 그렇습니다.

일반적으로 람다 식을 사용해야 하는 경우 사용 사례별로 평가해야 합니다 .

3.2. 메서드 참조 사용

마찬가지로 명령 개체를 호출자에 전달하기 위해 메서드 참조를 사용할 수 있습니다.

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);

이 경우 구현은 여전히 ​​TextFile 인스턴스 를 만들어야 하므로 lambdas 를 사용하는 것보다 조금 더 장황 합니다.

4. 결론

이 기사에서는 명령 패턴의 주요 개념과 개체 지향 접근 방식 및 람다 식과 메서드 참조의 조합을 사용하여 Java에서 패턴을 구현하는 방법을 배웠습니다.

늘 그렇듯이 이 예제에 표시된 모든 코드 예제는 GitHub 에서 사용할 수 있습니다 .

Generic footer banner