Rust語言之GoF設計模式:責任鏈模式
責任鏈是一種行為設計模式,它允許沿著潛在處理程式鏈傳遞請求,直到其中一個處理請求。
責任鏈也稱為職責鏈,功能鏈或過濾器模式,當有很多過濾器,無法依附於原有被過濾的物件,可以獨立出來成為獨立通用的一個大的過濾器集合時,就從decorator模式轉為責任鏈模式了。
先看看stackoverflow上一個錯誤的責任鏈實現:
pub trait Policeman<'a> { fn set_next(&'a mut self, next: &'a Policeman<'a>); } pub struct Officer<'a> { deduction: u8, next: Option<&'a Policeman<'a>>, } impl<'a> Officer<'a> { pub fn new(deduction: u8) -> Officer<'a> { Officer {deduction, next: None} } } impl<'a> Policeman<'a> for Officer<'a> { fn set_next(&'a mut self, next: &'a Policeman<'a>) { self.next = Some(next); } } fn main() { let vincent = Officer::new(8); // -+ vincent 進入生命週期範圍scope let mut john = Officer::new(5); // -+ john 進入生命週期範圍scope let mut martin = Officer::new(3); // -+ martin 進入生命週期範圍scope // | john.set_next(&vincent); // | martin.set_next(&john); // | } |
這會產生錯誤訊息:
error[E0597]: `john` does not live long enough --> src\main.rs:29:1 | 27 | john.set_next(&vincent); | ---- borrow occurs here 28 | martin.set_next(&john); 29 | } | ^ `john` dropped here while still borrowed | = note: values in a scope are dropped in the opposite order they are created error[E0597]: `martin` does not live long enough --> src\main.rs:29:1 | 28 | martin.set_next(&john); | ------ borrow occurs here 29 | } | ^ `martin` dropped here while still borrowed | = note: values in a scope are dropped in the opposite order they are created error[E0597]: `john` does not live long enough --> src\main.rs:29:1 | 28 | martin.set_next(&john); | ---- borrow occurs here 29 | } | ^ `john` dropped here while still borrowed | = note: values in a scope are dropped in the opposite order they are created |
疑問:
為什麼john活得不夠長?
- 已建立vincent
- 已建立john
- 已建立martin
- john引用vincent(vincent在scope範圍內)
- martin引用john (john在scope範圍內)
- martin超出範圍(john仍在scope範圍內)
- john超出範圍(vincent仍在scope範圍內)
- vincent超出範圍
這裡問題是:
如果您在Rust中多行多個位置使用相同的生命週期引數,編譯器必須找到同時滿足所有三個位置的具體生命週期。它會混亂。
在編譯:
john.set_next(&vincent); |
時,編譯器會在vincent和 john之間混亂。。。
解決辦法
使用泛型將實現範圍縮小到嚴格的編譯時型別似乎相當困難:為了構造一個完整鏈的型別,Rust 需要完全瞭解鏈中的“下一個”連結。因此,它看起來像這樣:
let mut reception = Reception::<Doctor::<Medical::<Cashier>>>::new(doctor); |
只有使用Box,Box允許以任何組合進行連結:
use super::{into_next, Department, Patient}; #[derive(Default)] pub struct Reception { next: Option<Box<dyn Department>>, } impl Reception { pub fn new(next: impl Department + 'static) -> Self { Self { next: into_next(next), } } } impl Department for Reception { fn handle(&mut self, patient: &mut Patient) { if patient.registration_done { println!("Patient registration is already done"); } else { println!("Reception registering a patient {}", patient.name); patient.registration_done = true; } } fn next(&mut self) -> &mut Option<Box<dyn Department>> { &mut self.next } } |
Department是職責鏈的統一介面:
mod cashier; mod doctor; mod medical; mod reception; pub use cashier::Cashier; pub use doctor::Doctor; pub use medical::Medical; pub use reception::Reception; use crate::patient::Patient; /// 組成一條鏈的統一角色。 /// 一個典型的trait介面實現必須有`handle`和`next`方法。 /// 而` execute'是預設實現,幷包含一個適當的鏈式 ////邏輯。 pub trait Department { fn execute(&mut self, patient: &mut Patient) { self.handle(patient); if let Some(next) = &mut self.next() { next.execute(patient); } } fn handle(&mut self, patient: &mut Patient); fn next(&mut self) -> &mut Option<Box<dyn Department>>; } /// Helps to wrap an object into a boxed type. pub(self) fn into_next( department: impl Department + Sized + 'static, ) -> Option<Box<dyn Department>> { Some(Box::new(department)) } |
這個Department介面trait下面三個真正職責鏈實現:
- doctor.rs
use super::{into_next, Department, Patient}; pub struct Doctor { next: Option<Box<dyn Department>>, } impl Doctor { pub fn new(next: impl Department + 'static) -> Self { Self { next: into_next(next), } } } impl Department for Doctor { fn handle(&mut self, patient: &mut Patient) { if patient.doctor_check_up_done { println!("A doctor checkup is already done"); } else { println!("Doctor checking a patient {}", patient.name); patient.doctor_check_up_done = true; } } fn next(&mut self) -> &mut Option<Box<dyn Department>> { &mut self.next } }
- medical.rs
- cashier.rs
reception.rs中使用Box<dyn Department>>原因:
Rust編譯器需要知道每個函式的返回型別需要多少空間。這意味著你的所有函式都必須返回一個具體的型別。與其他語言不同的是,如果你有一個像Department這樣的介面trait,你不能編寫一個直接返回Department的函式,因為Department的不同實現(如doctor、medical、cashier等)需要不同的記憶體。
然而,有一個簡單的變通方法。函式不用直接返回一個trait特質物件Department,而是返回一個包含一些Department的Box。
這個Box只是對堆中一些記憶體的引用,因為一個引用有一個靜態已知的大小,而且編譯器可以保證它指向一個堆中的Department。
Rust在分配堆上的記憶體時,會盡可能地明確。
因此,如果你的函式以這種方式返回一個指向堆上trait的指標,你需要用dyn關鍵字來寫返回型別,例如Box<dyn Department>。
客戶端呼叫程式碼:
mod department; mod patient; use department::{Cashier, Department, Doctor, Medical, Reception}; use patient::Patient; fn main() { let cashier = Cashier::default(); let medical = Medical::new(cashier); let doctor = Doctor::new(medical); let mut reception = Reception::new(doctor); let mut patient = Patient { name: "John".into(), ..Patient::default() }; // 接待處處理一個病人,把他交給鏈條中的下一個環節。 // 接待處 -> 醫生 -> 醫療 -> 收銀員。 // Reception -> Doctor -> Medical -> Cashier. reception.execute(&mut patient); println!("\nThe patient has been already handled:\n"); reception.execute(&mut patient); } |
相關文章
- Rust語言之GoF設計模式: 模板方法模式RustGo設計模式
- Rust語言之GoF設計模式:原型模式RustGo設計模式原型
- Rust語言之GoF設計模式:迭代器模式RustGo設計模式
- Rust語言之GoF設計模式:工廠模式RustGo設計模式
- Rust語言之GoF設計模式:中介者Mediator模式RustGo設計模式
- Rust語言之GoF設計模式:抽象工廠模式RustGo設計模式抽象
- Rust語言之GoF設計模式:Flyweight享元模式RustGo設計模式
- Rust語言之GoF設計模式:備忘錄Memento模式RustGo設計模式
- Rust語言之GoF設計模式:靜態工廠RustGo設計模式
- Rust語言之GoF設計模式: 直譯器Interpreter模式RustGo設計模式
- Rust語言之GoF設計模式:介面卡AdapterRustGo設計模式APT
- 設計模式 —— 責任鏈模式設計模式
- 設計模式-責任鏈模式設計模式
- 設計模式(責任鏈模式)設計模式
- 設計模式——責任鏈模式設計模式
- 設計模式(三) 責任鏈模式設計模式
- 設計模式(十四) 責任鏈模式設計模式
- 設計模式(十八):責任鏈模式設計模式
- 設計模式之——責任鏈模式設計模式
- 設計模式之責任鏈模式設計模式
- 設計模式之責任鏈模式——Java語言描述設計模式Java
- PHP 設計模式之責任鏈模式PHP設計模式
- 設計模式系列之「責任鏈模式」設計模式
- JAVA設計模式之責任鏈模式Java設計模式
- 極簡設計模式-責任鏈模式設計模式
- 設計模式(四)OkHttp的責任鏈模式設計模式HTTP
- 設計模式第七講-責任鏈模式設計模式
- 我的Java設計模式-責任鏈模式Java設計模式
- Java學設計模式之責任鏈模式Java設計模式
- 23種設計模式(六)-責任鏈設計模式設計模式
- 設計模式 | 責任鏈模式及典型應用設計模式
- 設計模式(9)-責任鏈模式詳解(易懂)設計模式
- 責任鏈模式模式
- 設計模式:如何優雅地使用責任鏈模式設計模式
- 行為型設計模式 - 責任鏈模式詳解設計模式
- Go 實現常用設計模式(十)責任鏈模式Go設計模式
- 每天一個設計模式之責任鏈模式設計模式
- 設計模式--責任鏈模式ChainOfResponsibility(行為型)設計模式AI