Javaのコマンドパターン

Javaのコマンドパターン

1. 概要

コマンドパターンは動作設計パターンであり、GoFの設計パターンの正式なリストの一部です。 簡単に言えば、パターンは、呼び出すメソッド、メソッドの引数、およびメソッドが属するオブジェクトを含む encapsulate in an object all the data required for performing a given action (command),を意図しています。

このモデルではdecouple objects that produce the commands from their consumersが可能であるため、このパターンは一般に生産者/消費者パターンとして知られています。

このチュートリアルでは、オブジェクト指向アプローチとオブジェクト機能アプローチの両方を使用してJavaでコマンドパターンを実装する方法を学習し、それがどのようなユースケースで役立つかを確認します。

2. オブジェクト指向の実装

従来の実装では、コマンドパターンにはimplementing four components: the Command, the Receiver, the Invoker, and the Clientが必要です。

パターンがどのように機能し、各コンポーネントが果たす役割を理解するために、基本的な例を作成しましょう。

テキストファイルアプリケーションを開発したいとします。 このような場合、テキストファイルのオープン、書き込み、保存など、テキストファイル関連の操作を実行するために必要なすべての機能を実装する必要があります。

したがって、アプリケーションを上記の4つのコンポーネントに分解する必要があります。

2.1. コマンドクラス

コマンドは、呼び出すメソッド、メソッド引数、およびメソッドを実装するオブジェクト(レシーバーと呼ばれる)を含む、store all the information required for executing an actionに対する役割を持つオブジェクトです。

コマンドオブジェクトがどのように機能するかをより正確に理解するために、1つのインターフェイスと2つの実装のみを含む単純なコマンドレイヤーの開発を始めましょう。

@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を定義し、2つの実装OpenTextFileOperationSaveTextFileOperation,が具体的なアクションを実行します。 前者はテキストファイルを開き、後者はテキストファイルを保存します。

コマンドオブジェクトの機能を確認することは明らかです。レシーバーオブジェクト、呼び出すメソッド、引数(この場合は、引数は必須ですが、可能性があります)。

the component that performs the file operations is the receiver (the TextFile instance)であることを強調する価値があります。

2.2. レシーバークラス

レシーバーは、performs a set of cohesive actionsのオブジェクトです。 これは、コマンドの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. 発動者クラス

呼び出し元は、knows how to execute a given command but doesn’t know how the command has been implemented.であるオブジェクトです。コマンドのインターフェイスのみを認識します。

場合によっては、実行者はコマンドの実行とは別に、コマンドを保存およびキューに入れます。 これは、マクロの記録や元に戻す/やり直し機能などの追加機能の実装に役立ちます。

この例では、コマンドオブジェクトを呼び出し、コマンドのexecute()メソッドを介してそれらを実行するための追加のコンポーネントが必要であることが明らかになります。 This is exactly where the invoker class comes into play

呼び出し元の基本的な実装を見てみましょう。

public class TextFileOperationExecutor {

    private final List textFileOperations
     = new ArrayList<>();

    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

TextFileOperationExecutorクラスは単なるthin layer of abstraction that decouples the command objects from their consumersであり、TextFileOperationコマンドオブジェクト内にカプセル化されたメソッドを呼び出します。

この場合、クラスはコマンドオブジェクトもListに格納します。 もちろん、操作の実行プロセスにさらに制御を追加する必要がない限り、これはパターンの実装では必須ではありません。

2.4. クライアントクラス

クライアントは、実行するコマンドと、それらを実行するプロセスのどの段階でコマンドを指定することによってcontrols the command execution processするオブジェクトです。

したがって、パターンの正式な定義でオーソドックスになりたい場合は、一般的な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から、ラムダ式とメソッド参照に基づいて、make the code a little bit more compact and less verboseへのオブジェクト関数アプローチを使用できます。

3.1. ラムダ式の使用

TextFileOperationインターフェースはfunctional interfaceであるため、TextFileOperationインスタンスを明示的に作成しなくても、pass command objects in the form of lambda expressions to the invokerを実行できます。

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

reduced the amount of boilerplate codeを使用したため、実装ははるかに合理化され、簡潔に見えます。

それでも、問題は依然として存在します。このアプローチは、オブジェクト指向のアプローチと比較して優れていますか?

まあ、それはトリッキーです。 ほとんどの場合、よりコンパクトなコードがより良いコードを意味すると想定する場合、実際にそうです。

As a rule of thumb, we should evaluate on a per-use-case basis when to resort to lambda expressions

3.2. メソッド参照の使用

同様に、passing command objects to the invoker:のメソッド参照を使用できます

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

この場合、TextFileインスタンスを作成する必要があったため、実装はa little bit more verbose than the one that uses lambdasです。

4. 結論

この記事では、コマンドパターンの主要な概念と、オブジェクト指向のアプローチとラムダ式とメソッド参照の組み合わせを使用してJavaでパターンを実装する方法を学びました。

いつものように、このチュートリアルに示されているすべてのコード例は利用可能なover on GitHubです。