Шаблон разработки команд
Шаблон команды — это один из шаблонов поведенческого проектирования. Шаблон проектирования команды используется для реализации слабой связи в модели запрос-ответ.
Шаблон команды
Пример шаблона разработки команды
Мы рассмотрим реальный сценарий, в котором мы можем реализовать шаблон Command. Допустим, мы хотим предоставить утилите файловой системы методы для открытия, записи и закрытия файла. Эта утилита файловой системы должна поддерживать несколько операционных систем, таких как Windows и Unix. Чтобы реализовать нашу утилиту файловой системы, прежде всего нам нужно создать классы-получатели, которые фактически будут выполнять всю работу. Поскольку мы кодируем с точки зрения интерфейса в java, у нас может быть интерфейс FileSystemReceiver
и его классы реализации для различных разновидностей операционных систем, таких как Windows, Unix, Solaris и т. д.
Классы приемников шаблонов команд
package com.journaldev.design.command;
public interface FileSystemReceiver {
void openFile();
void writeFile();
void closeFile();
}
Интерфейс FileSystemReceiver определяет контракт для классов реализации. Для простоты я создаю два вида классов-получателей для работы с системами Unix и Windows.
package com.journaldev.design.command;
public class UnixFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in unix OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in unix OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in unix OS");
}
}
package com.journaldev.design.command;
public class WindowsFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in Windows OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in Windows OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in Windows OS");
}
}
Заметили ли вы аннотацию Override, и если вам интересно, почему она используется, ознакомьтесь с преимуществами аннотации переопределения. Теперь, когда наши классы-приемники готовы, мы можем перейти к реализации наших классов Command.
Интерфейс шаблонов команд и реализации
Мы можем использовать интерфейс или абстрактный класс для создания нашей базовой команды, это дизайнерское решение и зависит от ваших требований. Мы собираемся с интерфейсом, потому что у нас нет реализаций по умолчанию.
package com.journaldev.design.command;
public interface Command {
void execute();
}
Теперь нам нужно создать реализации для всех различных типов действий, выполняемых получателем. Поскольку у нас есть три действия, мы создадим три реализации команд. Каждая реализация команды будет пересылать запрос соответствующему методу получателя.
package com.journaldev.design.command;
public class OpenFileCommand implements Command {
private FileSystemReceiver fileSystem;
public OpenFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
//open command is forwarding request to openFile method
this.fileSystem.openFile();
}
}
package com.journaldev.design.command;
public class CloseFileCommand implements Command {
private FileSystemReceiver fileSystem;
public CloseFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.closeFile();
}
}
package com.journaldev.design.command;
public class WriteFileCommand implements Command {
private FileSystemReceiver fileSystem;
public WriteFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.writeFile();
}
}
Теперь у нас есть готовые реализации приемника и команды, поэтому мы можем перейти к реализации класса вызывающего.
Класс вызывающего шаблона команды
Invoker — это простой класс, который инкапсулирует команду и передает запрос объекту команды для его обработки.
package com.journaldev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
Наша реализация утилиты файловой системы готова, и мы можем приступить к написанию простой клиентской программы шаблона команд. Но перед этим я предоставлю служебный метод для создания соответствующего объекта FileSystemReceiver
. Поскольку мы можем использовать шаблон Factory для возврата соответствующего типа на основе ввода.
package com.journaldev.design.command;
public class FileSystemReceiverUtil {
public static FileSystemReceiver getUnderlyingFileSystem(){
String osName = System.getProperty("os.name");
System.out.println("Underlying OS is:"+osName);
if(osName.contains("Windows")){
return new WindowsFileSystemReceiver();
}else{
return new UnixFileSystemReceiver();
}
}
}
Теперь давайте перейдем к созданию нашей клиентской программы примера шаблона команд, которая будет использовать нашу утилиту файловой системы.
package com.journaldev.design.command;
public class FileSystemClient {
public static void main(String[] args) {
//Creating the receiver object
FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
//creating command and associating with receiver
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
//Creating invoker and associating with Command
FileInvoker file = new FileInvoker(openFileCommand);
//perform action on invoker object
file.execute();
WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
file = new FileInvoker(writeFileCommand);
file.execute();
CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
file = new FileInvoker(closeFileCommand);
file.execute();
}
}
Обратите внимание, что клиент отвечает за создание соответствующего типа объекта команды. Например, если вы хотите записать файл, вы не должны создавать объект CloseFileCommand
. Клиентская программа также отвечает за присоединение приемника к команде, а затем команду к классу вызывающего. Вывод приведенной выше примерной программы шаблона команды:
Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS
Диаграмма класса шаблона команды
Важные моменты шаблона команды
- Команда — это ядро шаблона проектирования команды, определяющего контракт для реализации.
- Реализация приемника отделена от реализации команды.
- Класс реализации команды выбрал метод для вызова объекта-приемника, для каждого метода в приемнике будет реализация команды. Он работает как мост между получателем и методами действия.
- Класс Invoker просто перенаправляет запрос от клиента объекту команды.
- Клиент должен создать экземпляр соответствующей команды и реализации приемника, а затем связать их вместе.
- Клиент также отвечает за создание экземпляра объекта-вызова, связывание с ним объекта-команды и выполнение метода действия.
- Шаблон разработки команд легко расширяется, мы можем добавлять новые методы действий в приемники и создавать новые реализации команд без изменения клиентского кода.
- Недостаток шаблона проектирования Command заключается в том, что код становится огромным и запутанным из-за большого количества методов действий и множества ассоциаций.
Пример шаблона разработки команды JDK
Интерфейс Runnable (java.lang.Runnable) и действие Swing (javax.swing.Action) используют шаблон команды.