Design Patterns
Routing & Messages
Chain of Responsibility, Mediator, Visitor, and Memento — behavioral patterns for decoupling senders from receivers and managing complex interactions.
Suggest an editRouting & Messages
As systems grow, direct communication between objects leads to tight coupling. A button shouldn't need to know the memory address of the database service. These patterns focus on routing messages, decoupling senders from receivers, and managing complex networks of interacting objects.
💡 The core idea. You can place intermediary objects between senders and receivers. This allows you to chain handlers, centralize communication, execute operations without changing class structures, or capture snapshots of state.
You'll be able to:
- Pass a request along a chain of handlers until one handles it (Chain of Responsibility).
- Centralize complex communication between objects to prevent a chaotic web of references (Mediator).
- Add new operations to a class hierarchy without modifying the classes themselves (Visitor).
- Capture and restore an object's internal state without violating encapsulation (Memento).
📘 How to read the Intuition boxes. As you read, look for the Mechanism and Concrete bite under each code block. They map the code you just saw to the mental model you need to build.
1. Chain of Responsibility Pattern
The Chain of Responsibility passes a request along a chain of handlers. Upon receiving a request, each handler decides either to process it or to pass it to the next handler in the chain.
Instead of writing a massive block of authentication, logging, and caching checks inside a web controller, you split them into separate middlewares.
Output:
--- Logging INFO ---
Standard Console::Logger: This is an information.
--- Logging DEBUG ---
File::Logger: This is a debug level information.
Standard Console::Logger: This is a debug level information.
--- Logging ERROR ---
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.Analysis. An ERROR message enters the chain. ErrorLogger processes it and passes it on. FileLogger sees that ERROR > DEBUG, processes it, and passes it on. ConsoleLogger sees that ERROR > INFO, processes it, and the chain ends. (Note: standard HTTP middleware usually stops the chain when a handler fully resolves a request, rather than propagating it to every handler).
Intuition.
- Mechanism. Each handler contains a reference to the next handler. It either processes the request or forwards it.
- Concrete bite. Tech support. You call the level 1 agent. If they can't help, they pass you to level 2. If level 2 can't help, you go to level 3.
💡 Earned rule. Use Chain of Responsibility to decouple a request's sender from its receiver, allowing multiple objects a chance to handle it. The cost is that a request might reach the end of the chain unhandled.
2. Mediator Pattern
The Mediator Pattern centralizes complex communications and control logic between objects in a system.
Instead of aircraft communicating directly with each other to avoid collisions (which requires every plane to know the location of every other plane), they all communicate with a central Air Traffic Control tower.
Output:
Alice sends: Hello everyone!
Bob receives: Hello everyone!
Charlie receives: Hello everyone!Analysis. Users don't hold references to other users. They only hold a reference to the ChatMediator. This eliminates the many-to-many relationship (where 10 users would require 90 direct references) and replaces it with a one-to-many relationship to the mediator.
Intuition.
- Mechanism. Extract all communication logic between multiple objects into a dedicated Mediator class. Components communicate only with the Mediator.
- Concrete bite. A smart home hub. The motion sensor doesn't talk to the lights directly. It tells the hub it detected motion, and the hub turns on the lights.
💡 Earned rule. Use Mediator to reduce chaotic dependencies between interacting objects. The cost is that the Mediator can grow into a bloated "God Object" that knows too much about everything.
3. Visitor Pattern
The Visitor Pattern lets you add new operations to existing classes without changing them.
If you have a complex hierarchy (like a Document with Paragraphs, Images, and Tables) and you want to export it to PDF, HTML, or Markdown, putting an exportPDF(), exportHTML(), and exportMD() method inside every node class breaks the Single Responsibility Principle.
Output:
Book tax: 0 (Total: 500)
Fruit tax: 20.0 (Total: 220.0)Analysis. This uses a technique called Double Dispatch. The client calls item.accept(visitor). The item (e.g., Book) then calls visitor.visit(this). Because Java and Python resolve the this (or self) reference at runtime, the visitor knows exactly which concrete visit(Book) or visit_book method to execute.
Intuition.
- Mechanism. Implement an
accept(Visitor)method on elements. Implement overloadedvisit(Element)methods on the Visitor. - Concrete bite. An insurance agent (Visitor) visiting different types of buildings (Elements). The agent evaluates a house differently than a factory. The buildings just open the door (
accept), and the agent executes the correct evaluation logic based on the building type.
💡 Earned rule. Use Visitor to perform operations on a stable hierarchy of classes without polluting their code. The cost is that adding a new Element to the hierarchy forces you to update every existing Visitor.
4. Memento Pattern
The Memento Pattern captures and externalizes an object's internal state so it can be restored later, all without violating encapsulation.
Output:
Current: This is the first sentence. This is the second.
Restored: This is the first sentence. Analysis. The History (Caretaker) holds the Memento objects but never inspects or modifies their contents. The Editor (Originator) is the only class that extracts state from the Memento. This protects the Editor's encapsulation.
Intuition.
- Mechanism. The Originator creates an immutable Memento containing its state. A Caretaker stores the Memento and can pass it back to the Originator later to restore the state.
- Concrete bite. Video game save files. You (the Caretaker) tell the game (the Originator) to save. It hands you a file (the Memento). You can't edit the file, but you can pass it back to the game later to resume playing.
💡 Earned rule. Use Memento when you need to produce snapshots of an object's state to be able to restore a previous state. The cost is high RAM usage if the state is massive and snapshots are frequent.
Summary
| Pattern | Purpose |
|---|---|
| Chain of Responsibility | Passes a request along a chain of potential handlers. |
| Mediator | Centralizes communication between objects to prevent a tangled web of dependencies. |
| Visitor | Adds operations to a stable hierarchy of objects without modifying their classes. |
| Memento | Captures and restores an object's state without violating encapsulation. |
🚨 Gotcha Checklist
| Symptom | Likely cause | Fix |
|---|---|---|
You try to add a new Video class to an element hierarchy, and suddenly you have to edit 14 different Visitor classes. |
Unstable hierarchy. Visitor is only meant for object hierarchies that rarely change. | If the hierarchy changes often, don't use Visitor. Put the operations directly inside the classes instead. |
| Two objects communicate with each other, then one triggers the Mediator, which triggers the other, creating an infinite loop. | Mediator circular dependency. Complex event handling in a Mediator can easily loop back on itself. | Use flags or careful event tracing to ensure events only propagate one way. |
✅ Check yourself
Why shouldn't the Caretaker just read the fields directly from the Editor and store them?
Because that breaks encapsulation. The Editor would have to make its internal fields public, allowing any class to modify them. By using a Memento, the Editor packages its state securely, and the Caretaker holds it blindly.
📚 Sources
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
🧪 Predict, then check. In the Chain of Responsibility, does the invoker know which handler will eventually process the request?
No. The invoker simply hands the request to the first handler in the chain. It is up to the handlers to decide who processes it, which decouples the sender from the receiver.
Your Turn
Think of an Express.js or Spring Boot application. The request passes through authentication, rate limiting, and body parsing before reaching your controller. This is the Chain of Responsibility. Write a dummy chain of three handlers that checks if a user is logged in, then checks if they have admin rights, then prints "Success".