1概要

コマンドパターンはビヘイビアデザインパターンであり、https://en.wikipedia.org/wiki/Design__Patterns[GoF]の正式なデザインパターンリストの一部です。簡単に言うと、パターンは、呼び出すメソッド、メソッドの引数、およびメソッドが属するオブジェクトなど、特定のアクション(コマンド)の実行に必要なすべてのデータをオブジェクトにカプセル化することを目的としています。

このモデルを使用すると、コマンドを生成するオブジェクトをその消費者から切り離すことができます。そのため、このパターンが一般的にプロデューサ – コンシューマパターンとして知られています。

このチュートリアルでは、オブジェクト指向とオブジェクト機能の両方のアプローチを使用して、Javaでコマンドパターンを実装する方法を学びます。また、どのような場合に役立つかを説明します。


2オブジェクト指向実装

古典的な実装では、コマンドパターンは4つのコンポーネントを実装することを要求します:Command、Receiver、Invoker、そしてClient **

パターンの仕組みと各コンポーネントの役割を理解するために、基本的な例を作成しましょう。

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

そのため、アプリケーションを上記の4つのコンポーネントに分割します。


2.1. コマンドクラス

コマンドとは、コールするメソッド、メソッドの引数、メソッドを実装するオブジェクト(レシーバと呼ばれる)など、アクションの実行に必要なすべての情報を** 格納することを目的としたオブジェクトです。

コマンドオブジェクトがどのように機能するかをより正確に理解するために、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を定義し、

OpenTextFileOperation

と__SaveTextFileOperationの2つの実装が具体的なアクションを実行します。前者はテキストファイルを開き、後者はテキストファイルを保存します。


TextFileOperation

commandsはテキストファイルを開いたり保存したりするために必要なすべての情報をカプセル化しています。これにはレシーバオブジェクト、呼び出すメソッド、引数(この場合は引数はありません)必須ですが、そうなる可能性があります。

ファイル操作を実行するコンポーネントがレシーバ(

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. 呼び出し側クラス

実行者とは、与えられたコマンドの実行方法を知っているが、そのコマンドがどのように実装されているかを知らないオブジェクトです。

場合によっては、呼び出し側はコマンドの実行とは別に、コマンドを保管してキューに入れることもあります。これは、マクロの記録や元に戻す/やり直し機能など、いくつかの追加機能を実装するのに役立ちます。

この例では、コマンドオブジェクトを呼び出し、それらをコマンドの__execute()メソッドを介して実行する役割を果たす追加のコンポーネントが必要であることが明らかになります。

これがまさにinvokerクラスが登場するところです

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

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

インタフェースはhttps://docs.oracle.com/javase/8/docs/api/java/util/functions/packages-summary.html[関数型インタフェース]なので、次の形式でコマンドオブジェクトを渡すことができます。

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

インスタンスを作成する必要があるため、実装はラムダを使用する実装よりも少し冗長です**


4結論

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

いつものように、このチュートリアルに示されているすべてのコード例はhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns/src/main/java/com/baeldung/command[over on GitHubにあります。]。