Перейти к содержанию

Дизайн шаблонов в Rust - команда, простая реализация универсального шаблона

Posted on:28 апреля 2023 г. at 10:32

Example Dynamic OG Image link

Введение

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

Выглядит так:

![[0 PypmzCvxKfXfFpHj.webp]]

Короткое устройство этой диаграммы:

Реализация в 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);
  }
}

Объяснение:

  1. Структура MultiplyCommand имеет два поля i32.
  2. Мы задаем их в конструкторе new().
  3. Метод 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();
    }
  }
}

Некоторое объяснение:

  1. Структура Invoker содержит команды списка в виде Vec<Box<dyn Command>>. Команда dyn необходима, потому что нам нужна динамическая диспетчеризация, поскольку команда является трейтом. Box необходимо, поскольку размер объектов, добавленных в вектор, неизвестен.
  2. В конструкторе new() этот вектор инициализируется.
  3. Метод add_command() добавляет к вектору новую структуру, реализующую трейт Command.
  4. В методе 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();
}

Объяснение:

  1. Создается объект Invoker. Это должно быть изменяемым, так как мы собираемся добавить к нему команды.
  2. Затем мы добавим две команды: одну команду PrintCommand и одну команду Multiplycommand. Обратите внимание на то, как обе эти команды передают Box.
  3. Затем мы выполним все эти команды.

Вывод

Шаблон команды имеет множество применений, таких как программирование GUI, мобильный код или параллельная обработка. Как вы можете видеть здесь, реализация может быть довольно простой. Одним из моих следующих проектов будет изучение того, как мы можем использовать этот шаблон одновременно в многопоточной среде.