Interview contents
Mediator Pattern
Estimated time: 20 minutes
Goal: Understand the intent, participants, structure, common use-cases, and a small code example (Python + Rust) so you can explain the pattern in an interview and implement a basic mediator in production code.
1. Motivation / When to use
When many objects interact with each other, the interaction logic can become complex and tightly coupled. The Mediator Pattern centralizes this interaction into a single object — the mediator — reducing direct dependencies between colleagues and making the system easier to maintain and evolve.
Common scenarios:
- Complex communication protocols between components (UI widgets, subsystems).
- Preventing a many-to-many object relationship explosion.
- Encapsulating control logic that doesn’t belong to any single component.
2. Intent (one-liner)
Define an object that encapsulates how a set of objects interact. Promote loose coupling by keeping objects from referring to each other explicitly, and let the mediator handle communications.
3. Participants
- Mediator (interface / abstract): Declares methods for communication between colleagues.
- ConcreteMediator: Implements mediator behavior, knows and maintains references to colleagues, coordinates interaction.
- Colleague (interface / abstract): Declares an interface for components that communicate through the mediator.
- ConcreteColleague: Implements behavior and communicates via the mediator instead of calling other colleagues directly.
4. Structure (UML-like)
+---------------+ uses +------------------+| ConcreteCol- |<------------------>| Mediator || lague A | | (ConcreteMediator)|+---------------+ +------------------+ ^ ^ | |+---------------+ +------------------+| ConcreteCol- | | other colleagues || lague B | +------------------++---------------+Colleagues send messages to the mediator; the mediator decides which colleague(s) should receive the information and calls them.
5. Simple example (Python)
from __future__ import annotationsfrom typing import Protocol
class Mediator(Protocol): def notify(self, sender: object, event: str) -> None: ...
class ConcreteMediator: def __init__(self): self._col_a = None self._col_b = None
def register_a(self, col): self._col_a = col
def register_b(self, col): self._col_b = col
def notify(self, sender, event: str) -> None: if event == "A_done": print("Mediator reacts on A_done and triggers B.action_b().") self._col_b.action_b() elif event == "B_done": print("Mediator reacts on B_done and triggers A.action_a().") self._col_a.action_a()
class ColleagueA: def __init__(self, mediator: Mediator): self._mediator = mediator
def action_a(self): print("ColleagueA does A and notifies mediator") self._mediator.notify(self, "A_done")
class ColleagueB: def __init__(self, mediator: Mediator): self._mediator = mediator
def action_b(self): print("ColleagueB does B and notifies mediator") self._mediator.notify(self, "B_done")
# Usagem = ConcreteMediator()a = ColleagueA(m)b = ColleagueB(m)m.register_a(a)m.register_b(b)
# Start chaina.action_a()Notes: colleagues never call each other directly; they call mediator.notify(...) with events or messages.
6. Compact example (Rust)
// Simple mediator in Rust using trait objectsuse std::rc::Rc;use std::cell::RefCell;
trait Mediator { fn notify(&self, sender: &str, event: &str);}
struct ConcreteMediator { a: RefCell<Option<Rc<dyn Colleague>>>, b: RefCell<Option<Rc<dyn Colleague>>>,}
impl ConcreteMediator { fn new() -> Self { Self { a: RefCell::new(None), b: RefCell::new(None) } }}
impl Mediator for ConcreteMediator { fn notify(&self, sender: &str, event: &str) { if sender == "A" && event == "A_done" { if let Some(b) = &*self.b.borrow() { b.receive("Mediator: triggered B") } } }}
trait Colleague { fn receive(&self, message: &str);}
struct ColleagueA { mediator: Rc<dyn Mediator> }impl Colleague for ColleagueA { fn receive(&self, message: &str) { println!("ColleagueA received: {}", message); }}
// (Omitted: wiring up instances to keep snippet short for lesson)Note: Rust example is intentionally compact; wiring requires Rc and borrowing. In production, prefer explicit ownership models and channels for asynchronous mediation.
7. Pros & Cons
Pros:
- Reduces coupling between components.
- Centralizes control logic and protocol decisions.
- Easier to change interaction logic in one place.
Cons:
- Mediator can become a God Object if it grows too complex.
- Adds indirection, which can make flow harder to trace unless documented.
8. Alternatives and related patterns
- Observer: more decentralized; observers independently react to events.
- Facade: simplifies interface but doesn’t manage bi-directional interactions between peers.
- Event Bus / Pub-Sub: decouples senders and receivers via topics; good for large distributed systems.
9. Interview checklist (what to say in 2–3 minutes)
- State the intent: centralize interaction to reduce coupling.
- Describe participants: Mediator + Colleagues.
- Explain trade-offs: simpler coupling vs potential God Object.
- Give a short example: UI widgets notifying mediator which updates other widgets.
- End with alternatives: Observer and Pub-Sub.
10. Quick quiz (2 questions)
- Why might you choose an event bus over a mediator? (Answer: scalability and decentralization when many producers/consumers exist.)
- What is a primary risk when moving many responsibilities into the mediator? (Answer: it can become a God Object and hard to maintain.)
11. References / further reading
- Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software
- Martin Fowler — articles on Mediator / Event-driven architectures
Prepared for: interview-section/design-patterns — Mediator Pattern (20 min)