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

Шаблоны проектирования в Rust - Стратегия

Posted on:13 апреля 2023 г. at 09:18

Example Dynamic OG Image link

Вступление

Шаблон стратегии - это шаблон проектирования поведения, который позволяет вам определить семейство алгоритмов, инкапсулировать их в виде объекта и с помощью признаков сделать их взаимозаменяемыми.

На что это похоже?

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

  1. Контекст хочет применить какой-то алгоритм, и для этой цели он содержит объект, который реализует интерфейс стратегии.
  2. Когда требуется стратегия, инициируется объект StrategyA или StrategyB и вызывается метод algorithm.

Можно было бы утверждать, что это форма внедрения зависимостей, поскольку методы вызываются только в интерфейсе, а не в конкретных объектах.

Реализация в Rust

Откройте свой терминал в пустом каталоге и введите:

cargo new strategy_pattern
cd strategy_pattern

Затем откройте свою IDE и откройте main.rs в папке src.

Мы начнем с определения TravelStrategy, поэтому добавьте в main.rs:

trait TravelStrategy {
  fn travel(&self,distance: i16);
}

Затем мы определим TrainStrategy:

struct TrainStrategy;

impl TravelStrategy for TrainStrategy {
  fn travel(&self,distance: i16) {
    println!("Пройдено {} км",distance);
  }
}

Структура TrainStrategy пустая. Мы реализуем в ней метод перемещения, который просто выводит пройденное расстояние.

CarStrategy аналогична:

struct CarStrategy;

impl TravelStrategy for CarStrategy {
  fn travel(&self,distance: i16) {
    println!("Пройдено {} км",distance);
  }
}

Теперь, чтобы иметь возможность отправить всех в путь, мы начнем с определения TravelHub:

struct TravelHub<T:TravelStrategy> {
  strategy:T,
}

Здесь мы используем дженерики, T:TravelStrategy означает, что у нас может быть любой тип, если он реализует признак TravelStrategy.

Почему дженерики? Дженерики облегчают передачу в конкретном классе, не прибегая к коробочным типам или чему-то подобному, и это делает код намного более читаемым.

Теперь пишем TravelHub:

impl<T:TravelStrategy> TravelHub<T> {
  fn new(strategy:T)->Self {
    Self {
      strategy: strategy
    }
  }

  fn customer_travel(&self,distance:i16) {
    self.strategy.travel(distance);
  }
}

Краткое пояснение:

  1. Сначала мы определяем конструктор, который получает конкретный класс в качестве своего параметра. Этот класс должен реализовывать атрибут TravelStrategy.
  2. Затем у нас есть customer_travel, который просто вызывает метод путешествия по выбранной стратегии.

Посмотрим, сработает ли это:

fn main() {
  let travelhub=TravelHub::new(TrainStrategy{});
  travelhub.customer_travel(20);
}
  1. Создайте экземпляр travelhub с TrainStrategy в качестве параметра. Поскольку TrainStrategy реализует свойство TravelStrategy, мы можем передать это в качестве параметра.
  2. Вызовите метод customer_travel, чтобы посмотреть, все ли работает.

Вывод

Как вы можете видеть, реализация довольно проста. Использование дженериков позволяет обойти использование коробчатых конструкций. Это делает код намного более читабельным.

Этот шаблон также можно было бы назвать формой внедрения зависимостей, поскольку вызываются только методы в интерфейсах, где вызывающий не знает о базовом классе.