Rust語言之GoF設計模式:中介者Mediator模式

banq 發表於 2022-09-26
設計模式 Go

中介者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);
        }
    }
}