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