Введение
Шаблон команды является поведенческим шаблоном конструкции. Он используется вызывающим устройством для выполнения одного действия или ряда действий над объектами, называемых получателями. Вызывающий абонент не знает о характере принимающих объектов, он просто знает о команде. Таким образом мы достигаем свободной связи между Вызывающим и Получающим.
Выглядит так:
![[0 PypmzCvxKfXfFpHj.webp]]
Короткое устройство этой диаграммы:
Invoker
- это объект, который хочет выполнить команду или последовательность команд для некоторых неизвестных объектов, то есть вызывающий может не знать о них.- Интерфейс
Command
. Многие классы могут реализовать этот интерфейс. - В этом примере существует только один класс, реализующий интерфейс
Command
, который работает сCommandA
. Как вы видите,Invoker
иReceiver
не связаны, и единственная связь между ними осуществляется черезCommand
.
Реализация в Rust
В пустой папке откройте командную строку или терминал и введите:
cargo new rust_command
cd rust_command
Теперь откройте файл main.rs
в каталоге src/
.
Начнем с определения трейта Command:
trait Command {
fn execute(&self);
}
Для простоты имеет только метод: execute()
, который выполняет команду.
Начнем с определения очень простой команды:
struct PrintCommand {
message: String,
}
impl PrintCommand {
fn new(message: String) -> PrintCommand {
PrintCommand { message }
}
}
impl Command for PrintCommand {
fn execute(&self) {
println!("{}", self.message);
}
}
Этот код вполне самозаверяющий: у нас есть конструктор, new()
, который устанавливает сообщение. Далее мы реализуем метод execute()
трейта Command
, который просто печатает сообщение.
Несколько более сложный пример:
struct MultiplyCommand {
a: i32,
b: i32,
}
impl MultiplyCommand {
fn new(a: i32, b: i32) -> MultiplyCommand {
MultiplyCommand { a, b }
}
}
impl Command for MultiplyCommand {
fn execute(&self) {
let result=self.a*self.b;
println!("{} * {} = {}",self.a,self.b, result);
}
}
Объяснение:
- Структура
MultiplyCommand
имеет два поляi32
. - Мы задаем их в конструкторе
new()
. - Метод
execute()
перемножает эти поля и выводит соответствующее сообщение.
Нам также нужен объект для вызова или выполнения следующих команд:
struct Invoker {
commands: Vec<Box<dyn Command>>,
}
impl Invoker {
fn new() -> Invoker {
Invoker {
commands: Vec::new(),
}
}
fn add_command(&mut self, command: Box<dyn Command>) {
self.commands.push(command);
}
fn execute_commands(&self) {
for command in self.commands.iter() {
command.execute();
}
}
}
Некоторое объяснение:
- Структура
Invoker
содержит команды списка в видеVec<Box<dyn Command>>
. Командаdyn
необходима, потому что нам нужна динамическая диспетчеризация, поскольку команда является трейтом.Box
необходимо, поскольку размер объектов, добавленных в вектор, неизвестен. - В конструкторе
new()
этот вектор инициализируется. - Метод
add_command()
добавляет к вектору новую структуру, реализующую трейтCommand
. - В методе
execute_commands()
выполняется итерация по вектору и выполнение каждой команды.
Теперь мы можем проверить его:
fn main() {
let mut invoker=Invoker::new();
invoker.add_command(Box::new(PrintCommand::new("Hello World".to_string())));
invoker.add_command(Box::new(MultiplyCommand::new(2,3)));
invoker.execute_commands();
}
Объяснение:
- Создается объект
Invoker
. Это должно быть изменяемым, так как мы собираемся добавить к нему команды. - Затем мы добавим две команды: одну команду
PrintCommand
и одну командуMultiplycommand
. Обратите внимание на то, как обе эти команды передаютBox
. - Затем мы выполним все эти команды.
Вывод
Шаблон команды имеет множество применений, таких как программирование GUI, мобильный код или параллельная обработка. Как вы можете видеть здесь, реализация может быть довольно простой. Одним из моих следующих проектов будет изучение того, как мы можем использовать этот шаблон одновременно в многопоточной среде.