
Ланцюжок обов'язків на Rust
Ланцюжок обов’язків — це поведінковий патерн, що дозволяє передавати запит ланцюжком потенційних обробників до тих пір, поки один з них не обробить його.
Позбавляє від жорсткої прив’язки відправника запиту до одержувача, дозволяючи динамічно вибудовувати ланцюг з різних обробників.
Conceptual Example
The example demonstrates processing a patient through a chain of departments. The chain of responsibility is constructed as follows:
Patient -> Reception -> Doctor -> Medical -> Cashier
The chain is constructed using Box
pointers, which means dynamic dispatch in runtime. Why? It seems quite difficult to narrow down implementation to a strict compile-time typing using generics: in order to construct a type of a full chain Rust needs full knowledge of the "next of the next" link in the chain. Thus, it would look like this:
let mut reception = Reception::<Doctor::<Medical::<Cashier>>>::new(doctor); // 😱
Instead, Box
allows chaining in any combination:
let mut reception = Reception::new(doctor); // 👍 let mut reception = Reception::new(cashier); // 🕵️♀️
patient.rs: Request
#[derive(Default)] pub struct Patient { pub name: String, pub registration_done: bool, pub doctor_check_up_done: bool, pub medicine_done: bool, pub payment_done: bool, }
department.rs: Handlers
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; /// A single role of objects that make up a chain. /// A typical trait implementation must have `handle` and `next` methods, /// while `execute` is implemented by default and contains a proper chaining /// logic. 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 fn into_next(department: impl Department + Sized + 'static) -> Option<Box<dyn Department>> { Some(Box::new(department)) }
department/cashier.rs
use super::{Department, Patient}; #[derive(Default)] pub struct Cashier { next: Option<Box<dyn Department>>, } impl Department for Cashier { fn handle(&mut self, patient: &mut Patient) { if patient.payment_done { println!("Payment done"); } else { println!("Cashier getting money from a patient {}", patient.name); patient.payment_done = true; } } fn next(&mut self) -> &mut Option<Box<dyn Department>> { &mut self.next } }
department/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 } }
department/medical.rs
use super::{into_next, Department, Patient}; pub struct Medical { next: Option<Box<dyn Department>>, } impl Medical { pub fn new(next: impl Department + 'static) -> Self { Self { next: into_next(next), } } } impl Department for Medical { fn handle(&mut self, patient: &mut Patient) { if patient.medicine_done { println!("Medicine is already given to a patient"); } else { println!("Medical giving medicine to a patient {}", patient.name); patient.medicine_done = true; } } fn next(&mut self) -> &mut Option<Box<dyn Department>> { &mut self.next } }
department/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 } }
main.rs: Client code
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 handles a patient passing him to the next link in the chain. // Reception -> Doctor -> Medical -> Cashier. reception.execute(&mut patient); println!("\nThe patient has been already handled:\n"); reception.execute(&mut patient); }
Output
Reception registering a patient John Doctor checking a patient John Medical giving medicine to a patient John Cashier getting money from a patient John The patient has been already handled: Patient registration is already done A doctor checkup is already done Medicine is already given to a patient Payment done