TypeScript Mediator Pattern Example

1. Definition

The Mediator Design Pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

2. Problem Statement

When a system has multiple objects, the communication between them can get complex. Direct communication between a large number of classes makes the system hard to understand, maintain, and expand.

3. Solution

The Mediator pattern centralizes external communications. Instead of communicating with each other directly, objects communicate via the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling.

4. Real-World Use Cases

1. Chat rooms where users send messages to the chat, and the chat directs these messages to other participants.

2. Air traffic control centers, where the control tower (mediator) ensures that planes can take off and land without interfering with each other.

3. GUI libraries where components (buttons, text boxes, etc.) communicate events through a central controller.

5. Implementation Steps

1. Create a Mediator interface that defines the communication interface.

2. Implement a ConcreteMediator class that centralizes and manages the communication between different objects.

3. Define classes (Colleagues) that communicate with each other via the mediator.

6. Implementation in TypeScript

// Step 1: Mediator Interface
interface Mediator {
    send(message: string, colleague: Colleague): void;
}
// Step 2: Concrete Mediator
class ConcreteMediator implements Mediator {
    private colleague1: Colleague1;
    private colleague2: Colleague2;
    setColleague1(colleague: Colleague1): void {
        this.colleague1 = colleague;
    }
    setColleague2(colleague: Colleague2): void {
        this.colleague2 = colleague;
    }
    send(message: string, colleague: Colleague): void {
        if (colleague === this.colleague1) {
            this.colleague2.notify(message);
        } else {
            this.colleague1.notify(message);
        }
    }
}
// Step 3: Colleague classes
abstract class Colleague {
    protected mediator: Mediator;
    constructor(mediator: Mediator) {
        this.mediator = mediator;
    }
}
class Colleague1 extends Colleague {
    send(message: string): void {
        this.mediator.send(message, this);
    }
    notify(message: string): void {
        console.log("Colleague1 gets message:", message);
    }
}
class Colleague2 extends Colleague {
    send(message: string): void {
        this.mediator.send(message, this);
    }
    notify(message: string): void {
        console.log("Colleague2 gets message:", message);
    }
}
// Client code
const mediator = new ConcreteMediator();
const colleague1 = new Colleague1(mediator);
const colleague2 = new Colleague2(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.send("Hello, Colleague2!");
colleague2.send("Hi, Colleague1!");

Output:

Colleague2 gets message: Hello, Colleague2!
Colleague1 gets message: Hi, Colleague1!

Explanation:

The above TypeScript code defines a Mediator pattern with two colleagues. The colleagues do not communicate with each other directly. Instead, they use the mediator to send and receive messages. 

The ConcreteMediator manages this communication, ensuring that messages sent by one colleague are received by the other.

7. When to use?

Use the Mediator pattern when:

1. You want to reduce the complexity and dependencies between classes that communicate directly with each other.

2. You want to centralize external communications instead of distributing them among objects.

3. You want to create a more maintainable and adaptable system by decoupling classes.


Comments