责任链是一种行为设计模式,它允许沿着潜在处理程序链传递请求,直到其中一个处理请求。
责任链也称为职责链,功能链或过滤器模式,当有很多过滤器,无法依附于原有被过滤的对象,可以独立出来成为独立通用的一个大的过滤器集合时,就从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中多行多个位置使用相同的生命周期参数,编译器必须找到同时满足所有三个位置的具体生命周期。它会混乱。
在编译:
时,编译器会在vincent和 john之间混乱。。。解决办法
使用泛型将实现范围缩小到严格的编译时类型似乎相当困难:为了构造一个完整链的类型,Rust 需要完全了解链中的“下一个”链接。因此,它看起来像这样:
let mut reception = Reception::<Doctor::<Medical::<Cashier>>>::new(doctor);
|
只有使用Box,Box允许以任何组合进行链接:
reception.rs
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是职责链的统一接口:
department.rs
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); }
|