What is Mediator pattern?
Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.
Mediator enables decoupling of objects by introducing a layer in between so that the interaction between objects happen via the layer. If the objects interact with each other directly, the system components are tightly-coupled with each other that makes higher maintainability cost and not hard to extend.
Mediator pattern focuses on providing a mediator between objects for communication and help in implementing lose-coupling between objects.
Participants
Mediator
It defines the interface for communication between colleague objectsConcreteMediator
It implements the mediator interface and coordinates communication between colleague objectsColleague
It defines the interface for communication with other colleaguesConcreteColleague
It implements the colleague interface and communicates with other colleagues through its mediator
Real-World
Air traffic controller is a great example of mediator pattern where the airport control room works as a mediator for communication between different flights. Mediator works as a router between objects and it can have it’s own logic to provide way of communication.
When to use Mediator pattern?
- It's hard to change some of the classes because they are tightly coupled to a bunch of other classes
- You can't reuse a component in a different program because it's too dependent on other components
- You find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts
Advantages
It limits subclassing. A mediator localizes behavior that otherwise would be distributed among several objects. Changing this behaviour requires subclassing Mediator only, Colleague classes can be reused as is.
Disadvantage
It centralizes control. The mediator pattern trades complexity of interaction for complexity in the mediator. Because a mediator encapsulates protocols, it can become more complex than any individual colleague. This can make the mediator itself a monolith that’s hard to maintain
Code Examples
train_station.rs
use std::collections::{HashMap, VecDeque};
use crate::trains::Train;
// 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);
}
#[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);
}
}
}
}
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);
}
}
}
trains/mod.rs
mod freight_train;
mod passenger_train;
pub use freight_train::FreightTrain;
pub use passenger_train::PassengerTrain;
use crate::train_station::Mediator;
// 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);
}
trains/freight_train.rs
use super::Train;
use crate::train_station::Mediator;
pub struct FreightTrain {
name: String,
}
impl FreightTrain {
pub fn new(name: &'static str) -> Self {
Self { name: name.into() }
}
}
impl Train for FreightTrain {
fn name(&self) -> &String {
&self.name
}
fn arrive(&mut self, mediator: &mut dyn Mediator) {
if !mediator.notify_about_arrival(&self.name) {
println!("Freight train {}: Arrival blocked, waiting", self.name);
return;
}
println!("Freight train {}: Arrived", self.name);
}
fn depart(&mut self, mediator: &mut dyn Mediator) {
println!("Freight train {}: Leaving", self.name);
mediator.notify_about_departure(&self.name);
}
}
trains/passenger_train.rs
use super::Train;
use crate::train_station::Mediator;
pub struct PassengerTrain {
name: String,
}
impl PassengerTrain {
pub fn new(name: &'static str) -> Self {
Self { name: name.into() }
}
}
impl Train for PassengerTrain {
fn name(&self) -> &String {
&self.name
}
fn arrive(&mut self, mediator: &mut dyn Mediator) {
if !mediator.notify_about_arrival(&self.name) {
println!("Passenger train {}: Arrival blocked, waiting", self.name);
return;
}
println!("Passenger train {}: Arrived", self.name);
}
fn depart(&mut self, mediator: &mut dyn Mediator) {
println!("Passenger train {}: Leaving", self.name);
mediator.notify_about_departure(&self.name);
}
}
main.rs
mod train_station;
mod trains;
use train_station::TrainStation;
use trains::{FreightTrain, PassengerTrain};
fn main() {
let train1 = PassengerTrain::new("Train 1");
let train2 = FreightTrain::new("Train 2");
// Station has `accept` and `depart` methods,
// but it also implements `Mediator`.
let mut station = TrainStation::default();
// Station is taking ownership of the trains.
station.accept(train1);
station.accept(train2);
// `train1` and `train2` have been moved inside,
// but we can use train names to depart them.
station.depart("Train 1");
station.depart("Train 2");
station.depart("Train 3");
}
Top comments (0)