Поиск по сайту:

Шаблон разработки команд


Шаблон команды — это один из шаблонов поведенческого проектирования. Шаблон проектирования команды используется для реализации слабой связи в модели запрос-ответ.

Шаблон команды

Пример шаблона разработки команды

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