Behavioral Patterns
Mediator
Reduce coupling between communicating objects by forcing them to collaborate solely through a centralized mediator object.
The Mediator Pattern is a behavioral design pattern that reduces chaotic dependencies between objects. It restricts direct communication between objects and forces them to collaborate only via a mediator object, turning a complex network of interdependencies (spaghetti code) into a clean star topology.
1. Learning Objectives
- Understand how the Mediator pattern decouples complex mesh networks of communicating components.
- Distinguish between the Mediator, Observer, and Facade design patterns.
- Learn how to handle lifecycle management and avoid circular references (memory leaks) using weak references in C++ and Python.
- Identify the "God Object" anti-pattern and how to design clean, bounded mediators.
- Implement a fully functional smart home automation hub mediating between sensors, lights, and alarms.
2. Problem & Naive Solution
Imagine you are building a Smart Home Automation System. You have several independent devices:
- MotionSensor: Detects movement in a room.
- Light: Illuminates the room.
- Thermostat: Regulates room temperature.
- Alarm: Sounds a siren during security breaches.
You want these devices to cooperate dynamically. For instance, when the motion sensor detects movement, it should turn on the light (if it is dark), turn up the thermostat, and trigger the alarm if the security system is armed.
The Naive Solution
In a naive implementation, each device maintains direct references to all other devices it needs to interact with:
This direct-link model scales poorly. As you add more components (e.g., smart blinds, security cameras, sprinkler systems), every component must hold references to almost every other component. The system collapses into a chaotic $N \times N$ dependency grid.
3. Issues
- High Coupling: Changing the interface or behavior of a single device (e.g., adding a param to
turnOn()) requires modifying multiple other classes that hold references to it. - Zero Reusability: You cannot reuse a single component (e.g.,
MotionSensor) in a different setup (like a simple office lighting system) because it is tightly coupled toThermostatandAlarm. - Hard to Test: Mocking the dependencies of a single component requires creating mocks for all related devices in the system, making unit testing extremely difficult.
4. Pattern Introduction & UML
The Mediator Pattern solves this by centralizing communication. Instead of objects talking directly to one another, they notify a mediator object when an event occurs. The mediator encapsulates the coordination logic and routes commands to the appropriate target components.
UML Class Diagram: Smart Home Hub
UML Sequence Diagram: Centralized Event Routing
5. Participants
- Mediator (
SmartHomeMediator): Interface defining the communication protocol with components. - Concrete Mediator (
SmartHomeHub): Coordinates the colleague components, holding references to them and enforcing business workflows. - Colleague Base (
Device): Base class containing a reference to the mediator. Colleague classes do not know about other colleagues; they only communicate with their mediator. - Concrete Colleagues (
Light,Alarm,MotionSensor): Implement individual actions. When they perform an action or experience an event, they notify the mediator.
6. Theory
The core intent of the Mediator pattern is to turn many-to-many relationships into one-to-many relationships.
- Decoupling: Colleagues are completely blind to each other. You can change, add, or remove colleague classes without touching other colleague classes.
- Mediator vs. Observer:
- Observer establishes dynamic one-way connections where subjects publish events to observers without knowing who they are.
- Mediator centralizes the communication channel. The components send events to the mediator, and the mediator directs specific actions back to targeted components. They can be combined: colleagues publish events to the mediator (Observer), and the mediator coordinates response actions (Mediator).
- Mediator vs. Facade:
- Facade provides a simplified interface to a complex subsystem. Communication flows one-way from the client through the facade to the subsystem.
- Mediator coordinates bidirectional communication between sibling objects. Subsystem components talk back to the mediator.
7. Syntax Explanation & Memory Safety
Because the Mediator holds references to the Colleagues, and the Colleagues hold references back to the Mediator, you have circular references. This requires special care in languages without garbage collectors or with reference-counted smart pointers:
- C++ Circular Dependencies & Memory Leaks: If both the mediator and colleague classes hold
std::shared_ptrto each other, a circular reference cycle occurs, preventing objects from ever being deallocated. To solve this, the colleague should store astd::weak_ptror a raw reference, while the mediator storesstd::shared_ptr. - Python Reference Cycles: Similar to C++, keeping strong back-references can delay garbage collection or create leaks. Using
weakref.ref(mediator)resolves this issue. - Java Object References: The JVM's Garbage Collector handles circular references cleanly during reachability analysis, but explicit unregistration is recommended to avoid "lapsed listener" leaks if components are dynamically added and removed.
8. Step-by-Step Implementation
- Define the
Mediatorinterface containingtriggerEventandregisterDevicefunctions. - Create the abstract
Colleagueclass wrapping a mediator reference. - Implement concrete colleagues (
Light,Alarm,MotionSensor) inheriting fromColleague. Ensure they invokemediator.triggerEvent()instead of talking to other classes. - Implement the
ConcreteMediatorclass (SmartHomeHub). Store references to the concrete colleagues. - In the Concrete Mediator, implement routing rules inside
triggerEvent()to coordinate behavior between components. - Write client code registering the components and simulating actions.
9. Complete Code (Mini Project)
10. Code Walkthrough
Let's trace how the Mediator coordinate flows without direct references:
- Colleague Independence: The class
MotionSensorhas no compilation reference toLightorAlarm. When it triggers, it callsmediator.triggerEvent(). - Weak Reference Usage: In the C++ and Python examples, back-links to the mediator are stored as weak pointers (
std::weak_ptrandweakref.ref), preventing circular memory leaks when the hub is destroyed. - Central Coordination: The
SmartHomeHubregisters devices dynamically and holds concrete business logic insidetriggerEvent. Changing how the light responds to motion requires modifying *only* the hub, not the sensor or light.
11. Execution Flow
- The client instantiates the
SmartHomeHubmediator. - Colleague classes are constructed and passed the mediator pointer.
- Colleagues are registered with the mediator, which stores their pointers in a map.
hallwaySensor.detectMotion()is called.- The sensor calls
mediator.triggerEvent("HW_SENSOR_01", "MOTION_DETECTED"). - The mediator loops through registered devices, identifies the light and alarm, and executes their target actions.
12. Internal Working & Memory Analysis
Without a mediator, the heap graph looks like a tangled web. With a mediator, we introduce a central star structure:
- Decoupled Heap References: The devices hold a single pointer (
mediator) pointing to the mediator object. The mediator's hash map (devices) holds pointers pointing back to all concrete devices. - Clean Deregistration: When a device is unregistered, the mediator drops its reference, allowing the GC (or smart pointer destructors) to immediately reclaim the memory. Without a mediator, you would have to manually find and remove references in all other sibling devices.
13. Complexity Analysis
- Time Complexity: $O(1)$ event dispatch time from a colleague's perspective. The mediator routes events in $O(N)$ where $N$ is the number of target devices to notify, or $O(1)$ if mapped directly to event listeners.
- Space Complexity: $O(N)$ where $N$ is the number of registered colleague components stored in the mediator.
- Design Trade-offs: You swap complex communication links ($O(N^2)$ dependencies) for simpler components, but code complexity shifts inside the mediator class ($O(N)$ coordination branches).
14. Best Practices
- Guard Against God Objects: Do not put raw device logic (like light dimming algorithm details) inside the mediator. The mediator should only direct flow (e.g., call
light.turnOn()). Keep device implementation details inside the device. - Combine with Observer: Use the Observer Pattern to let colleagues dynamically subscribe to the mediator, making the event routing registration loose and open to runtime modifications.
15. Common Mistakes
- Spaghetti Mediator: Packing entire application logic inside a single massive mediator class, making it hard to read and debug.
- Direct Colleague Bypassing: Allowing some colleagues to keep direct references and communicate around the mediator, breaking the pattern's decoupling guarantee.
- Forgetting Weak Pointers: Creating memory leak issues in C++ or Python by storing strong shared pointer references in both directions.
16. Framework Usage
- Java Swing / Desktop GUI: Layout panels and control elements (buttons, sliders) do not talk to each other. They notify an Action Listener / Controller (the Mediator), which adjusts values, disables fields, or updates labels.
- Spring Integration: Uses message channels and message endpoints. Components send messages to a centralized channel (mediator), and routing rules distribute them to target receivers.
- State Managers (Redux): Actions are dispatched to a central Store (Mediator) which updates state and alerts UI components. UI components never modify each other's state directly.
17. Interview Discussion
Answer: The mediator itself can grow extremely complex over time. As more features and coordination rules are added, it can turn into a difficult-to-maintain "God Object" containing massive conditional routing logic.
Answer: Observer focuses on establishing a one-to-many dependency channel dynamically. Subjects do not know who is listening. Mediator focuses on centralizing communication between arbitrary sibling objects. The colleagues explicitly know they are talking to a mediator, which directs execution to specific colleagues.
Answer: By applying single responsibility at the mediator level: split massive mediators into sub-mediators handling specific component clusters (e.g. security mediator vs lighting mediator), and delegate algorithmic operations to colleagues, keeping mediator methods thin.
18. Practice Exercises
- Easy: Write a simple chat room mediator program in Python where user objects can register and broadcast messages to all users *except* themselves.
- Medium: Build an Air Traffic Control tower system in C++ where flights register to land, and the mediator grants landing permissions, preventing multiple flights from using the same runway.
- Hard: Create a GUI form controller mediator in Java. It coordinates components: checking if
usernameandpasswordfields are filled, validating if theemailformat matches, and dynamically enabling thesubmitButtonwhile displaying real-time error labels.
19. Challenge Problem
Implement a Stock Trading Order Book Matching Engine. Sibling components represent Buyer and Seller classes. They submit buy/sell limit orders (containing price, volume, and stock symbol) to a centralized matching engine (the Mediator). The mediator coordinates the matching algorithm: finding matching buy/sell orders in the order book, processing transactions, transferring funds, and updating both parties. Ensure that buyers and sellers never talk directly, and write tests simulating multiple simultaneous transactions.
20. Summary & Cheat Sheet
- Mediator encapsulates communication between sibling classes to promote loose coupling.
- Swaps chaotic $O(N^2)$ direct coupling for a tidy star topology of $O(N)$ links.
- Use weak references (C++
weak_ptror Pythonweakref) for back-references to avoid circular memory leaks. - Keep mediator implementation simple to avoid creating a bloated God Object.
21. Quiz
1. What does the Mediator pattern seek to replace?
A) Global variables
B) Chaotic many-to-many communication meshes between sibling objects (Correct)
C) Single inheritance limits
2. Which topology matches a mediator pattern structure?
A) Mesh Topology
B) Ring Topology
C) Star Topology (Correct)
3. How do you prevent circular dependency memory leaks when implementing Mediator in C++?
A) Do not delete pointers
B) Use std::weak_ptr for colleague-to-mediator references (Correct)
C) Use raw pointer arrays exclusively
4. What is a key disadvantage of the Mediator design pattern?
A) It slows down compiler speed
B) The mediator can turn into a monolithic "God Object" that is hard to maintain (Correct)
C) It prevents polymorphism
5. How does Facade differ from Mediator?
A) Facade routes two-way message transfers
B) Facade provides a simplified interface downward to a subsystem, whereas Mediator coordinates dynamic two-way sibling interactions (Correct)
C) Facade only works in C++ applications
6. In Python, what library utility is used to protect against reference cycles in Mediator?
A) gc.collect
B) weakref (Correct)
C) ctypes
7. Can Mediator and Observer patterns be combined?
A) No, they are mutually exclusive
B) Yes, by using Observer to dynamically notify the mediator of colleague events (Correct)
C) Only inside GUI layout managers
8. Which of the following is a real-world example of Mediator in GUI frameworks?
A) Event dispatch listener controllers coordinating disabled/enabled element inputs (Correct)
B) File stream input buffers
C) Thread pool managers
9. What is the complexity order of colleague connections after applying Mediator?
A) $O(N^2)$
B) $O(N)$ (Correct)
C) $O(\log N)$
10. Where should the core algorithmic details of a colleague's action (e.g. dimmer voltage math) be kept?
A) In the mediator class
B) Inside the colleague class itself, keeping the mediator focused on routing flow (Correct)
C) Inside global configuration files
22. Next Lesson Preview
In the next lesson, we will explore the Memento Pattern. We will learn how to capture and restore an object's internal state without violating encapsulation—giving our LLD projects undo/redo history power!
Related Topics
- StrategyDefine a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients.
- IteratorAccess elements of an aggregate object sequentially without exposing its underlying representation (list, stack, tree, graph).
- ObserverDefine a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.