The Mediator design pattern is a behavioural design pattern that encapsulates how objects interact with one another, ensuring that they do not refer to each other explicitly. This reduces the dependencies between communicating objects, leading to a more loosely coupled system. The pattern introduces a mediator object that handles all communications and control between the objects.
Let's implement a simple example where we have a set of user objects that can send messages to each other via a chat mediator.
// Mediator interface
class Mediator {
send(message, colleague) {}
}
// ConcreteMediator
class ChatMediator extends Mediator {
constructor() {
super();
this.users = [];
}
addUser(user) {
this.users.push(user);
}
send(message, sender) {
this.users.forEach((user) => {
if (user !== sender) {
user.receive(message);
}
});
}
}
// Colleague
class User {
constructor(name, mediator) {
this.name = name;
this.mediator = mediator;
}
send(message) {
console.log(`${this.name} sends: ${message}`);
this.mediator.send(message, this);
}
receive(message) {
console.log(`${this.name} receives: ${message}`);
}
}
// Client
const mediator = new ChatMediator();
const user1 = new User("Alice", mediator);
const user2 = new User("Bob", mediator);
const user3 = new User("Charlie", mediator);
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("Hello everyone!");
user2.send("Hi Alice!");
In this implementation, we have a ChatMediator class that acts as the mediator, facilitating communication between User objects (colleagues). The User class interacts with the mediator to send and receive messages. The mediator maintains a list of users and ensures that messages sent by one user are received by all other users. In the client code, we create user instances and add them to the mediator. When a user sends a message, the mediator relays the message to all other users, demonstrating the Mediator pattern's ability to manage complex communication without direct coupling between user objects.
Let's create a more complex implementation of the Mediator design pattern by simulating an air traffic control system where planes communicate with the control tower (mediator) for takeoff and landing.
// Mediator interface
class Mediator {
notify(sender, event) {}
}
// ConcreteMediator
class ControlTower extends Mediator {
constructor() {
super();
this.runwayFree = true;
this.planes = [];
}
registerPlane(plane) {
this.planes.push(plane);
}
notify(sender, event) {
if (event === "readyForLanding") {
if (this.runwayFree) {
console.log(`ControlTower: Plane ${sender.id} is clear to land.`);
this.runwayFree = false;
} else {
console.log(`ControlTower: Plane ${sender.id} is waiting to land.`);
}
}
if (event === "landed") {
console.log(`ControlTower: Plane ${sender.id} has landed.`);
this.runwayFree = true;
this.checkWaitingPlanes();
}
if (event === "readyForTakeoff") {
if (this.runwayFree) {
console.log(`ControlTower: Plane ${sender.id} is clear to take off.`);
this.runwayFree = false;
} else {
console.log(`ControlTower: Plane ${sender.id} is waiting to take off.`);
}
}
if (event === "takenOff") {
console.log(`ControlTower: Plane ${sender.id} has taken off.`);
this.runwayFree = true;
this.checkWaitingPlanes();
}
}
checkWaitingPlanes() {
for (let plane of this.planes) {
if (plane.state === "waitingForLanding" && this.runwayFree) {
this.notify(plane, "readyForLanding");
plane.state = "landing";
break;
}
if (plane.state === "waitingForTakeoff" && this.runwayFree) {
this.notify(plane, "readyForTakeoff");
plane.state = "takingOff";
break;
}
}
}
}
// Colleague
class Plane {
constructor(id, mediator) {
this.id = id;
this.mediator = mediator;
this.state = "onGround";
mediator.registerPlane(this);
}
readyForLanding() {
console.log(`Plane ${this.id}: Ready for landing.`);
this.state = "waitingForLanding";
this.mediator.notify(this, "readyForLanding");
}
landed() {
console.log(`Plane ${this.id}: Landed.`);
this.state = "onGround";
this.mediator.notify(this, "landed");
}
readyForTakeoff() {
console.log(`Plane ${this.id}: Ready for takeoff.`);
this.state = "waitingForTakeoff";
this.mediator.notify(this, "readyForTakeoff");
}
takenOff() {
console.log(`Plane ${this.id}: Taken off.`);
this.state = "inAir";
this.mediator.notify(this, "takenOff");
}
}
// Client
const controlTower = new ControlTower();
const plane1 = new Plane("A1", controlTower);
const plane2 = new Plane("B2", controlTower);
const plane3 = new Plane("C3", controlTower);
plane1.readyForTakeoff();
plane2.readyForLanding();
plane3.readyForTakeoff();
plane1.takenOff();
plane2.landed();
In this complex implementation, we have a ControlTower class acting as the mediator, coordinating the takeoff and landing of Plane objects (colleagues). The Plane class communicates with the ControlTower to request takeoff and landing permissions. The ControlTower keeps track of the runway's availability and notifies planes accordingly. It also manages a queue of waiting planes to ensure that operations are conducted safely and efficiently. In the client code, we create plane instances and simulate their interactions with the control tower. Planes request takeoff and landing, and the control tower grants or delays these requests based on runway availability, demonstrating the Mediator pattern's effectiveness in managing complex, dependent interactions.