Стратегия

Паттерны проектирования / Поведенческие паттерны
image

trait Strategy {
    fn do_operation(&self, a: i32, b:i32) -> i32;
}

//ConcreteStrategy
struct AddStrategy {}
impl Strategy for AddStrategy {
    fn do_operation(&self, a: i32, b:i32) -> i32 {
        a + b
    }
}

//ConcreteStrategy
struct SubtractStrategy {}
impl Strategy for SubtractStrategy {
    fn do_operation(&self, a: i32, b:i32) -> i32 {
        a - b
    }
}

//Context
struct Calc {
    strategy: Option<Box<dyn Strategy>>
}
impl Calc {
    fn execute(&self, a: i32, b:i32) -> i32 {
        if let Some(strategy) = &self.strategy {
            return strategy.do_operation(a, b);
        }
        0
    }

    fn set_strategy(&mut self, strategy: Box<dyn Strategy>) {
        self.strategy = Some(strategy);
    }
}

let mut calc = Calc{ strategy: None };
let result1 = calc.execute(5, 3);
//result1 is 0

calc.set_strategy(Box::new(AddStrategy{}));
let result2 = calc.execute(5, 3);
//result2 is 8

calc.set_strategy(Box::new(SubtractStrategy{}));
let result3 = calc.execute(5, 3);
//result3 is 2

println!("result1 is {result1}");
println!("result2 is {result2}");
println!("result3 is {result3}");


Определяет семейство алгоритмов, инкапсулируя их и позволяя подставлять один вместо другого. Можно менять алгоритм независимо от клиента, который им пользуется.
Read more

Задача. Магазин считает стоимость доставки: по весу, по расстоянию или по фиксированной цене. Если один метод держит все способы в цепочке условий, он растёт с каждым новым способом. Правка одного способа может сломать другие, и ни один способ нельзя проверить отдельно.

Идея. Каждый способ сделать работу становится отдельным объектом - стратегией. У всех стратегий одинаковый вид: те же входные данные и тот же тип результата. Объект, которому нужна эта работа, называют контекстом. Он хранит одну стратегию и вызывает её, не зная, какая именно ему досталась. Стратегию выбирает клиентский код и может заменить её в любой момент.

Как читать пример. Каждая конкретная стратегия - это один алгоритм, и все они принимают одинаковые входные данные. Клиентский код выбирает стратегию. Код, который пользуется стратегией, только вызывает её и не знает, какую получил. В большинстве примеров одни и те же два числа дают другой результат после каждой смены стратегии. Большинство из них отвечают и на случай, когда стратегии нет. В некоторых языках стратегия - просто функция.

Когда применять. Стратегия нужна, когда одну работу можно сделать несколькими способами, а выбор зависит от пользователя, данных или настроек. Для одного алгоритма, который не будет меняться, она ни к чему. Стратегия похожа на Состояние, но разница в том, кто делает выбор. Здесь алгоритм выбирает снаружи клиентский код, и стратегии ничего не знают друг о друге. В Состоянии объект сам переходит из состояния в состояние.

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