Рубрики
Без рубрики

Шаблон команд в Java

Узнайте, как реализовать шаблон команды в Java с использованием объектно-ориентированных и объектно-функциональных подходов.

Автор оригинала: baeldung.

1. Обзор

Шаблон команды является поведенческим шаблоном проектирования и является частью формального списка шаблонов проектирования GoF . Проще говоря, шаблон намеревается инкапсулировать в объект все данные, необходимые для выполнения данного действия (команды), включая то, какой метод вызывать, аргументы метода и объект, к которому принадлежит метод.

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

В этом уроке мы узнаем, как реализовать шаблон команды в 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();
    }
}

В этом случае интерфейс Операции с текстовым файлом определяет API командных объектов, а две реализации OpenTextFileOperation и SaveTextFileOperation выполняют конкретные действия. Первый открывает текстовый файл, а второй сохраняет текстовый файл.

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

Стоит подчеркнуть, что компонентом, выполняющим файловые операции, является получатель (экземпляр Текстового файла ) .

2.2. Класс Приемника

Приемник-это объект, который выполняет набор взаимосвязанных действий . Это компонент, который выполняет фактическое действие при вызове метода execute () |/команды.

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

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 textFileOperations
     = new ArrayList<>();
    
    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

Класс Text File OperationExecutor |/- это всего лишь тонкий слой абстракции, который отделяет объекты команд от их потребителей и вызывает метод, инкапсулированный в объекты TextFileOperation command.

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

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 :

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, используя объектно-ориентированный подход и комбинацию лямбда-выражений и ссылок на методы.

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