Rust语言之GoF设计模式:策略模式

22-09-26 banq

策略将一组动作行为转化为对象(动词变名词),并使它们在原始上下文对象中可互换。

Rust实现
创建一个表示公共接口的trait并多次实现该trait:

trait Strategy {
    fn execute(&self);
}

struct ConcreteStrategyA;

impl Strategy for ConcreteStrategyA {
    fn execute(&self) {
        println!("ConcreteStrategyA")
    }
}

struct ConcreteStrategyB;

impl Strategy for ConcreteStrategyB {
    fn execute(&self) {
        println!("ConcreteStrategyB")
    }
}



然后实现持有一个实现该trait的泛型类型,Context上下文是一个持有trait实现的数据结构:

struct Context<S> {
    strategy: S,
}

impl<S> Context<S>
where
    S: Strategy,
{
    fn do_things(&self) {
        println!("Common preamble");
        self.strategy.execute();
        println!("Common postamble");
    }
}


知识点:

泛型类型、特征trait和生命周期

  • Rust中泛型:不是像i32或String之类的具体类型。我们可以表达泛型的行为或它们与其他泛型的关系,而不知道编译和运行代码时它们的位置。
    泛型可以为编译器提供有关引用如何相互关联的信息
  • 生命周期允许我们向编译器提供有关借用值的足够信息,以便它可以确保引用在更多情况下有效
  • 可以将接口trait特征与泛型类型结合起来,将泛型类型限制为仅接受具有特定行为的类型,而不是仅接受任何类型。


调用代码:

fn main() {
    let a = Context {
        strategy: ConcreteStrategyA,
    };
    a.do_things();

    println!("\n---\n");

    let b = Context {
        strategy: ConcreteStrategyB,
    };
    b.do_things();
}



第二个Rust案例
函数和闭包简化了策略实现,因为您可以将行为直接注入对象而无需复杂的接口定义。

type RouteStrategy = fn(from: &str, to: &str);

fn walking_strategy(from: &str, to: &str) {
    println!("Walking route from {} to {}: 4 km, 30 min", from, to);
}

fn public_transport_strategy(from: &str, to: &str) {
    println!(
        "Public transport route from {} to {}: 3 km, 5 min",
        from, to
    );
}

struct Navigator {
    route_strategy: RouteStrategy,
}

impl Navigator {
    pub fn new(route_strategy: RouteStrategy) -> Self {
        Self { route_strategy }
    }

    pub fn route(&self, from: &str, to: &str) {
        (self.route_strategy)(from, to);
    }
}

fn main() {
    let navigator = Navigator::new(walking_strategy);
    navigator.route("Home", "Club");
    navigator.route("Club", "Work");

    let navigator = Navigator::new(public_transport_strategy);
    navigator.route("Home", "Club");
    navigator.route("Club", "Work");

    let navigator = Navigator::new(|from, to| println!("Specific route from {} to {}", from, to));
    navigator.route("Home", "Club");
    navigator.route("Club", "Work");
}


似乎 Strategy 经常被隐含地广泛使用在 Rust 的现代开发中,例如它就像迭代器一样工作:

let a = [0i32, 1, 2];

let mut iter = a.iter().filter(|x| x.is_positive());


策略模式也是过滤器的一种实现,不过这种过滤器是一种策略过滤器,非常类似decorator模式,只是不依赖附加于一个固定对象,不同于职责链模式,每个策略过滤器只能有一个策略有效,如果多个策略依次有效,就是策略模式+责任链模式了。
策略模式不同于命令模式,命令模式中任何一个命令的发生都是事先无法控制预测的,而策略模式中策略激活都是事先谋划计划好的,如同诸葛亮的锦囊妙计,需要在特定上下文打开激活,这些策略都是事先计划好放入袋子中,当然料事如神诸葛亮可以这么干,一般人计划能力没有这么强,而且会产生认知心理焦虑,控制力太强并不有益于身心健康。

事先计划设计是一件很繁重的活动,见DDD战略设计 事件风暴 头脑会议。

如果想根据上下文动态执行对应策略,可以通过规则引擎 实现。