Шаблон команд в 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.

Чтобы понять, как работает шаблон и какую роль играет каждый компонент, давайте создадим базовый пример.

Предположим, мы хотим разработать приложение с текстовым файлом. В таком случае мы должны реализовать все функциональные возможности, необходимые для выполнения некоторых операций, связанных с текстовым файлом, таких как открытие, запись, сохранение текстового файла и т. Д.

Итак, мы должны разбить приложение на четыре компонента, упомянутых выше.

2.1. Командные классы

Команда - это объект, роль которого -store all the information required for executing an action, включая вызываемый метод, аргументы метода и объект (известный как получатель), реализующий метод.

Чтобы получить более точное представление о том, как работают командные объекты, давайте начнем разработку простого командного уровня, который включает только один интерфейс и две реализации:

@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,, выполняют конкретные действия. Первый открывает текстовый файл, а второй сохраняет текстовый файл.

Ясно видна функциональность командного объекта: командыTextFileOperationencapsulate all the information required для открытия и сохранения текстового файла, включая объект-получатель, методы для вызова и аргументы (в данном случае нет аргументы необходимы, но они могут быть).

Стоит подчеркнуть, что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. Класс Invoker

Вызывающий объект - это объект, который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.

Давайте посмотрим на базовую реализацию нашего invoker:

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, мы можемpass command objects in the form of lambda expressions to the invoker, без необходимости явно создавать экземплярыTextFileOperation:

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

В данном случае реализацией являетсяa little bit more verbose than the one that uses lambdas, так как нам все еще нужно было создать экземплярыTextFile.

4. Заключение

В этой статье мы узнали ключевые концепции шаблона команды и узнали, как реализовать шаблон в Java, используя объектно-ориентированный подход и комбинацию лямбда-выражений и ссылок на методы.

Как обычно, все примеры кода, показанные в этом руководстве, доступныover on GitHub.