Ланцюжок обов'язків на 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