Rust语言之GoF设计模式:中介者Mediator模式


中介者Mediator模式在Rust在实现很难,因为其他语言中的典型 Mediator 实现是 Rust 中的经典反模式:许多对象相互持有可变的交叉引用,试图相互变异,这在 Rust 中是一个致命的罪过——编译器不会通过你的第一个天真实施,除非它过于简单化。

根据定义,Mediator限制了对象之间的直接通信,并强制它们仅通过 mediator 对象进行协作。它也代表 MVC 模式中的控制器。

有一篇关于Rust 中的中介者模式的研究和讨论: https ://github.com/fadeevab/mediator-pattern-rust 。

交叉引用Rc<RefCell<..>>
不推荐这种方法,欺骗 Rust 编译器。
问题:

  1. 所有 trait 方法都是只读的:不可变self和不可变参数。
  2. Rc,RefCell在底层被广泛使用,负责从编译器到运行时的可变借用。无效的实现会导致运行时的恐慌。

自上而下的所有权
自上而下的所有权方法允许在 Rust 中应用 Mediator,因为它适用于具有严格借用检查器规则的 Rust 所有权模型。虽然不是实现 Mediator 的唯一方法,但它是一种基本比较好的方法。
关键是从所有权的角度思考:

1、中介者拥有所有组件的所有权。

2、组件不保留对中介者的引用。相反,它通过方法调用获取其引用。

// A train gets a mediator object by reference.
pub trait Train {
    fn name(&self) -> &String;
    fn arrive(&mut self, mediator: &mut dyn Mediator);
    fn depart(&mut self, mediator: &mut dyn Mediator);
}

// Mediator有提醒通知方法 has notification methods. 
pub trait Mediator {
    fn notify_about_arrival(&mut self, train_name: &str) -> bool;
    fn notify_about_departure(&mut self, train_name: &str);
}

3、控制流从main.rs的fn main()中介接收外部事件/命令的地方开始。


let train1 = PassengerTrain::new("Train 1");
let train2 = FreightTrain::new(
"Train 2");

// Station车站有`accept`和`depart`方法。
// 但是它也实现了`Mediator'。
let mut station = TrainStation::default();

// Station车站正在接受列车的所有权。
station.accept(train1);
station.accept(train2);

/ `train1`和`train2`已经被移到里面。
// 但我们可以用train的名字来 depart它们
station.depart(
"Train 1");
station.depart(
"Train 2");
station.depart(
"Train 3");


4、Mediator组件之间交互的trait方法是如notify_about_arrival, notify_about_departure通知提醒,这些动作与它用于接收外部事件的外部 API 是不同的(accept,depart是来自主循环的命令)。
车站Station的代码比较复杂:train_station.rs

下面是组件交互的动作notify_about_arrival, notify_about_departure:

#[derive(Default)]
pub struct TrainStation {
    trains: HashMap<String, Box<dyn Train>>,
    train_queue: VecDeque<String>,
    train_on_platform: Option<String>,
}

impl Mediator for TrainStation {
    fn notify_about_arrival(&mut self, train_name: &str) -> bool {
        if self.train_on_platform.is_some() {
            self.train_queue.push_back(train_name.into());
            false
        } else {
            self.train_on_platform.replace(train_name.into());
            true
        }
    }

    fn notify_about_departure(&mut self, train_name: &str) {
        if Some(train_name.into()) == self.train_on_platform {
            self.train_on_platform = None;

            if let Some(next_train_name) = self.train_queue.pop_front() {
                let mut next_train = self.trains.remove(&next_train_name).unwrap();
                next_train.arrive(self);
                self.trains.insert(next_train_name.clone(), next_train);

                self.train_on_platform = Some(next_train_name);
            }
        }
    }
}

同时再实现Train组件接口trait的方法:

impl TrainStation {
    pub fn accept(&mut self, mut train: impl Train + 'static) {
        if self.trains.contains_key(train.name()) {
            println!("{} has already arrived", train.name());
            return;
        }

        train.arrive(self);
        self.trains.insert(train.name().clone(), Box::new(train));
    }

    pub fn depart(&mut self, name: &'static str) {
        let train = self.trains.remove(name);
        if let Some(mut train) = train {
            train.depart(self);
        } else {
            println!(
"'{}' is not on the station!", name);
        }
    }
}