Design Patterns
Behavioural Patterns: Requests and Undo
The second lesson on behavioural patterns: Command, Chain of Responsibility and Memento, the patterns that turn requests and state into objects you can pass around, store and replay. A command undoes by restoring what it saw, not by guessing an inverse; a chain of handlers must decide its order and report requests nobody handles; a memento is taken before a change, and copies whatever is mutable. Every example runs in Java and Python, with verified output.
Suggest an editBehavioural Patterns: Requests and Undo — Command, Chain of Responsibility and Memento
Press a button on a remote, and the light goes on. Press undo, and it goes off. Raise a support ticket, and it ends up with the right team. Edit your resume, change your mind, and get yesterday's version back. Each of these needs the same trick: something that is normally a passing method call, a request or a piece of state, has to become an object that can be stored, handed on, or put back later.
This lesson covers the three patterns that do that. Command turns a request into an object, so it can be queued, logged and undone. Chain of Responsibility passes a request along a line of handlers until one of them deals with it. Memento captures an object's state in an opaque snapshot, so it can be restored without exposing the object's internals. It is the second of four lessons on behavioural patterns, after Strategy, Template Method and State.
💡 The core idea.
- A command object wraps one action: which receiver, which operation, which arguments. The code that triggers it (the invoker) doesn't know what it does, so commands can be assigned to buttons, stored in a history and undone.
- A chain of responsibility links handlers so that each either handles a request or passes it on. The sender knows only the first handler, and the order of the chain is part of the design.
- A memento is a snapshot of an object's state that only that object can read. A caretaker stores mementos without looking inside them, and hands one back to restore.
This builds on Behavioural Design Patterns and on the copying rules in Relationships & Object Behaviour, which a memento depends on. Every output below was produced by running the code on Java 21 and Python 3.11.
You'll be able to: wrap requests as command objects with execute() and undo(), keep a history, and make undo restore the state each command actually changed; build a chain of handlers in which the order is a deliberate choice and unhandled requests are always reported; take and restore mementos without exposing an object's fields, at the right moment and with mutable state copied; choose between undo by inverse commands and undo by snapshots.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what has been turned into an object, and who holds it.
- A concrete bite — a specific, runnable program where the request or the snapshot is handled at the wrong moment, or by the wrong object.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
- Command pattern
- Chain of Responsibility pattern
- Memento pattern
- Undo with commands or with mementos?
- Mental-model summary
- Gotcha checklist
- Check yourself
- Sources
1. Command pattern
A remote control sends commands to devices: turn on the lights, adjust the volume. The person holding it doesn't need to know how the devices work, only which buttons to press. The Command pattern brings that separation into code: a request becomes an object, which decouples the code that issues the request from the code that performs it.
📘 Definition. "Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations" [1].
Because a request is an object, it can be executed later, stored, logged or undone, and features such as undo, redo, logging and macros can be added without changing the business logic.
💡 Insight. With a remote you turn the lights or the air conditioner (AC) on and off. You don't need to know how the circuits work or how the AC receives the signal; you press "On" or "Off", and the remote sends the command.
The Command pattern decouples the sender of a request (the remote) from its receiver (the light or the AC) in the same way.
The four parts of the pattern.
- Client: creates the command objects and connects them to their receivers.
- Invoker: asks a command to execute, without knowing what it does (the remote).
- Command: binds a receiver to an action, behind an interface such as
execute()andundo(). - Receiver: knows how to actually perform the action (the light, the AC).
Understanding the problem
Here is a remote control that knows every device and every action, and remembers the last action for undo:
Output:
Light turned ON
AC turned ON
Light turned OFF
Light turned ON
Light turned OFFThe second undo turned the light off again instead of turning the AC off: the remote remembers only one action, so "undo" undid the previous undo.
Issues in the code.
- Tight coupling:
NaiveRemoteControlcallsLightandACdirectly, so every new device means changing the remote, which violates the Open/Closed Principle. - No flexibility: the actions are hard-coded methods; a new action or sequence of actions means editing the remote.
- Undo is tangled with the actions:
pressUndomust know the inverse of every action, which makes anything more complex than one level of undo very hard. - Hard-coded commands:
pressLightOn,pressACOnand the rest are fixed into the class, so buttons can't be reassigned. - No command history: there is no record of what has been done, only of the last action, so undo can't go back more than one step.
The solution
With the Command pattern, each action is an object with execute() and undo(). The remote holds commands in slots and a history of executed commands, and knows nothing about devices:
Output:
Light turned ON
AC turned ON
Light turned OFF
Light turned ON
AC turned OFF
Light turned OFF
No commands to undo.The history is a stack: Java's ArrayDeque (the Stack class is a legacy class whose own documentation recommends Deque instead), and a list in Python. In Python, a plain function could stand in for a command that only executes, but a command with undo() needs to remember its receiver and what it did, which a class expresses clearly.
How the Command pattern resolves the issues.
| Issue | How the Command pattern resolves it |
|---|---|
| Tight coupling | RemoteControl talks only to Command objects, never to devices. |
| No flexibility | A new device or action is a new Command class; the remote doesn't change. |
| Undo tangled with actions | Each command knows how to undo itself. |
| Hard-coded commands | Any command can be assigned to any slot at run time. |
| No command history | The remote keeps a stack of executed commands, so undo can go back any number of steps. |
Analysis. Three undos walked back through three actions, in reverse order, and a fourth reported that nothing was left. The remote's code never mentions a light or an AC; it only executes and undoes whatever commands it holds, in the order its history stack gives them back.
Intuition.
Mechanism. A command captures what to do as an object, when it is created; the invoker decides when to do it. Undo is then a question for each command: what must I do to put things back the way they were before I ran? Writing undo() as "the opposite action" is a guess about what the state was, and a guess is wrong whenever the action didn't change anything.
Concrete bite. The light is already off, and the user presses "off" anyway, then undo:
Output:
inverse undo: light on? true
restoring undo: light on? falseThe light was off, "off" changed nothing, and "undo" turned it on: a state the user never had. The restoring command recorded what it found when it ran, and undo put back exactly that.
💡 Earned rule. Use Command when requests must be assigned, queued, logged, undone or combined into macros. Make each command record, in execute(), whatever state it is about to change, and make undo() restore that, not apply a guessed opposite. Push a command onto the history only after it has executed successfully.
The cost is a class per action (or a small generic command that takes a function), plus memory for each command's saved state for as long as the history keeps it. Cap the history if commands are frequent.
Impact of not using the Command pattern.
- Invoker and receiver are tightly coupled: changes or additions mean modifying both.
- No reuse: without an abstraction for actions, the same action can't be reused elsewhere.
- Undo and redo are hard: they become complex and error-prone when operations are tied to specific methods.
- Batch actions are awkward: a "night mode" that turns several devices off has to be coded by hand.
- No plug-and-play: commands can't be added or changed without touching other code.
- Poor scalability: as the system grows, managing actions without a structure gets steadily harder.
When to use the Command pattern.
- Decoupling the sender from the receiver: the code that triggers an action shouldn't know what it does.
- Undo and redo: you need to reverse or repeat actions.
- Batch operations: several actions must run together, such as applying night mode.
- Plug-in architectures: new commands must be added without changing the core system.
- Macros or composite commands: a group of commands runs in sequence as one.
Pros of the Command pattern.
- Decouples sender and receiver: the invoker and the receivers can change independently.
- Supports undo and redo: each command carries its own way back.
- Extensible and reusable: new commands are new classes, and commands can be reused anywhere.
Cons of the Command pattern.
- More classes: one small class per action can add up.
- Overkill for simple tasks: a direct method call is simpler when nothing needs to be stored or undone.
- Undo needs careful design: commands must save the right state, especially in long or composite chains.
2. Chain of Responsibility pattern
A customer support system has several levels of support: general enquiries, billing, technical issues, delivery problems. A ticket should reach the team that can deal with it, without the customer, or the code that receives the ticket, knowing which team that is. The Chain of Responsibility pattern lines up the handlers so that each either deals with the request or passes it to the next.
📘 Definition. "Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it" [1].
The parts of the pattern.
- Handler: an abstract class or interface with a method for handling requests, and a reference to the next handler.
- Concrete handler: handles the request if it can, and otherwise forwards it to the next handler.
- Client: sends the request to the first handler, usually without knowing which handler will deal with it.
💡 Insight. A customer's request goes through a chain of support teams. Each team decides whether it can resolve the issue or should pass it on, so each handles only what it is best at, and the customer never needs to know about the chain.
How it works. The client sends a request to the first handler. If that handler can process it, it does; otherwise it forwards the request to the next one. This continues until the request is handled, or the end of the chain is reached. New handlers can be added to the chain without changing the existing ones.
Understanding the problem
An e-commerce platform's support system receives tickets of several types: general enquiries, refunds, technical issues and delivery complaints. Here is a first version:
Output:
Handled by General Support
Handled by Billing Team
Handled by Technical Support
Handled by Delivery Team
No handler availableIssues in this code.
| Issue | Description |
|---|---|
| Violates the Open/Closed Principle | Every new type of request means modifying handleRequest. |
| Monolithic code | All the teams' logic is in one method, which is hard to maintain, test and extend; the teams are coupled to each other. |
| Inflexible | The order of processing can't change, and handlers can't be added, without editing the core logic. |
The solution
With Chain of Responsibility, each team is a handler class. A handler deals with the requests it recognises and forwards the rest to the next handler:
Output:
BillingSupport: Handling refund request
DeliverySupport: Handling delivery issue
DeliverySupport: No handler found for requestHow Chain of Responsibility fixes the issues.
| Issue | Solution in the refactored code |
|---|---|
| Violates the Open/Closed Principle | A new request type is a new handler class, linked into the chain; existing handlers don't change. |
| Monolithic code | Each handler class deals with one type of request, which makes the code modular and easier to maintain. |
| Inflexible | Handlers are added, removed or reordered by changing how the chain is linked. |
Analysis. The client sent every ticket to general, and each one travelled along the chain until a handler recognised it. The "unknown" ticket was reported by DeliverySupport, but only because DeliverySupport happens to be last and happens to be the one class with a final else. The other handlers silently do nothing when they can't handle a request and have no next handler.
Intuition. Mechanism. A chain is a linked list of handlers, and the request walks it from the front. Two things are therefore part of the design, not details: the order, because the first handler that accepts a request wins; and the end of the chain, because something must happen to a request that no handler accepts.
Concrete bite. Someone moves the delivery team to the front of the chain, so delivery complaints are handled faster, and keeps the handler classes unchanged:
Output:
reordered chain:
(nothing was printed for the unknown ticket)
base class owns the end of the chain:
Unhandled request: unknownAfter the reorder, the unknown ticket reached TechnicalSupport, which has no next handler and no final else, so it vanished: no error, no log, and a customer whose ticket nobody will ever see. The only "not handled" message lived in one concrete handler, so it only worked while that handler was last. In the fixed version, the base class's handle() (a template method) owns forwarding and the end of the chain, so every handler reports unhandled requests, in any order.
💡 Earned rule. Use Chain of Responsibility when several handlers may deal with a request and the right one is decided at run time. Put the forwarding and the end-of-chain behaviour (report, log, or throw) in the base class, so concrete handlers only say what they can handle and how. Build the chain in one place, and treat its order as a documented decision.
The cost is indirection: following a request means walking the chain, and long chains add a little overhead to every request. If each request type maps to exactly one handler and the order never matters, a map from type to handler is simpler.
When to use Chain of Responsibility.
- Several objects could handle a request, and which one isn't known in advance: the request travels along the chain until one handles it.
- Senders and receivers should be decoupled: the sender only knows the first handler.
- The chain should be configurable: handlers can be added, removed or reordered, depending on conditions.
It suits systems that need that flexibility, such as support ticket routing, event handling, or any sequence of checks that depends on varying conditions.
Pros of Chain of Responsibility.
- Less coupling between sender and receiver: the sender doesn't need to know which handler will deal with the request.
- Easy to add or remove handlers: handler classes stay unchanged; only the chain's wiring changes.
- Single Responsibility and Open/Closed Principles: each handler does one job, and new handlers don't modify existing ones.
- Configurable order: the chain can be arranged differently for different situations.
Cons of Chain of Responsibility.
- Performance with long chains: a request may pass through many handlers before one deals with it.
- Harder debugging: the path a request takes is decided at run time, which is harder to trace.
- Requests may go unhandled: without an end-of-chain fallback, a request nobody handles simply disappears.
- Order matters: a wrongly ordered chain sends requests to the wrong handler, or drops them.
Real-life examples.
- Sign-up checks. Signing up may involve validating the email, checking the user's age, confirming acceptance of the terms, and verifying a CAPTCHA. Each check can be a handler that passes the request on if it succeeds, or stops it if it fails.
- Support ticket routing. Tickets go through general enquiries, billing, technical and delivery teams, each checking whether the ticket is for them. A new department is a new handler.
- Servlet filters and UI events. In a Java web application, each servlet
Filtercan handle a request, or pass it on withchain.doFilter(...). In a web page, a click event "bubbles" from the clicked element up through its parents until one of them handles it and stops it.
3. Memento pattern
In a document editor you make changes, and want to be able to undo them and get back to an earlier version. The editor shouldn't expose its internal structure to make that possible. Instead it stores a memento, a snapshot of its state at a point in time, which can later restore it, while the details stay hidden.
📘 Definition. "Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later" [1].
It is especially useful for undo, redo and rollback.
The three parts of the pattern.
- Originator: the object whose state is saved and restored.
- Memento: an object holding a snapshot of the originator's state.
- Caretaker: the object that asks for mementos and keeps them, without changing or examining their contents.
💡 Insight. A text editor's undo works like this: as you type, the application captures snapshots of the document. Each snapshot (memento) is kept by an external caretaker, such as a history stack, and the editor (originator) can return to any of them without revealing how it stores the text.
The key strength of the pattern is that only the originator creates its snapshots and reads them back, so its encapsulation is preserved even though its state can be recovered.
Understanding the problem
A resume editor lets a user change their name, education, experience and skills, and should let them undo changes. Here is a first attempt:
Output:
After changes:
Name: Alice Johnson
Skills: [Java, SQL, Spring Boot]
After undo:
Name: Alice
Skills: [Java, SQL]Issues in this code.
- No caretaker:
main()handles the snapshot by hand; nothing manages several saved states. - No undo stack: only one snapshot is supported, so there is only one level of undo.
- Breaks encapsulation: the snapshot's fields are public, and so are the editor's, which exposes the internal details.
- Tight coupling:
ResumeSnapshotreaches intoResumeEditor's fields directly; if the editor's fields change, the snapshot class must change too. - No abstraction: how snapshots are created and restored is visible to, and changeable by, any code.
The solution
With the Memento pattern, the editor itself creates and reads its mementos, the memento is immutable and opaque to everyone else, and a caretaker keeps the history:
Output:
Experience: SDE Intern at a tech company | Skills: [Java, DSA, LLD, Spring Boot]
Experience: SDE Intern at a tech company | Skills: [Java, DSA, LLD]
Experience: Fresher | Skills: [Java, DSA]How the Memento pattern solves the issues.
| Issue | How the Memento pattern fixes it |
|---|---|
| No caretaker | ResumeHistory manages all mementos and performs undo. |
| Only one level of undo | A stack of mementos allows any number of undo steps. |
| Public fields | The editor's fields are private, and the memento's fields are private, final and readable only by ResumeEditor. |
| Tight coupling | The memento is created and read by the editor itself, so no other class depends on the editor's fields. |
| Snapshot logic spread around | Saving and restoring live inside ResumeEditor, which improves cohesion. |
The pattern gives the job of creating snapshots to the owner of the state, the originator. It has full access to its own fields, so it is the one object that can make a complete, accurate snapshot, without exposing them. In Java, the nested class's private fields are accessible to ResumeEditor but not to ResumeHistory; in Python, the privacy is a convention marked by the leading underscore.
Analysis. Each undo stepped back exactly one change: first the added "Spring Boot", then the internship. ResumeHistory stored and returned mementos without ever reading a field, and the mementos can't be changed after they are made, because their fields are final and the skills list is immutable (a tuple in Python).
Intuition.
Mechanism. A memento records the state at the moment it is taken. Undo restores the most recent memento, so for undo to move backwards, the most recent memento must hold the state from before the latest change. When to call save() is therefore the whole design: before each change, never after.
Concrete bite. Here the same editor and caretaker are used the way many first attempts use them: make a change, then save:
Output:
save after, one undo: draft 2
save before, one undo: draft 1With saving after each change, the first undo "restored" the state the editor was already in, so the user pressed undo and nothing happened; they would need a second press to get anywhere. Saving before each change made the first undo do what the user expects.
💡 Earned rule. Use Memento when an object's state must be restorable and its fields must stay private. Let the originator create and read its own mementos, make mementos immutable (copy or freeze every mutable field when saving), and take a memento before each change, ideally inside the operation that makes the change, so callers can't get the timing wrong.
The cost is memory: every memento holds a full copy of the state. For large objects or frequent changes, cap the history, or store only the parts that changed.
When to use the Memento pattern.
- Undo and redo: you need to save states and go back to them.
- Encapsulation matters: the object's state must be saved without exposing its private fields.
- Non-trivial history: you need several checkpoints or rollbacks, managed in a structured way.
Pros of the Memento pattern.
- Preserves encapsulation: the originator saves and restores its own state without exposing its structure.
- Simple undo and redo: stored snapshots make both straightforward.
- Clear separation of concerns: the originator handles its state, and the caretaker handles the history.
Cons of the Memento pattern.
- Memory use: many or large snapshots can use a lot of memory.
- Caretaker complexity: the caretaker must manage when mementos are created, stored and discarded.
- Old mementos need pruning: without limits, the history keeps growing.
Real-life use cases.
- Text editors. Each edit can store the document's previous state as a memento; undo restores the most recent one, and redo moves forward again, without any other code touching the document's internals.
- Graphics and design tools. Drawing applications save a snapshot of the canvas or a component after each significant operation (drawing, colouring, transforming), so users can step back to any earlier state, which keeps editing non-destructive.
4. Undo with commands or with mementos?
Both patterns in this lesson can implement undo, in different ways:
| Undo with Command | Undo with Memento | |
|---|---|---|
| What is stored | each action, plus the small piece of state it changed | a full snapshot of the object's state |
| How undo works | the command reverses its own effect | the originator is reset to the snapshot |
| Memory per step | small | the size of the whole state |
| Good when | actions are small and well defined (toggle a light, move a shape) | the state is small, or changes are hard to reverse (a complex edit, a reformat) |
| Risk | an undo() that guesses wrong (section 1) |
a snapshot taken at the wrong time, or sharing mutable data (section 3) |
They also combine well: a command can take a memento of its receiver in execute(), and restore it in undo(). That gives every command a correct undo, at the cost of a snapshot per command.
5. Mental-model summary
| Principle | Consequence |
|---|---|
| Command: a request as an object (receiver + action) | It can be assigned to buttons, queued, logged and undone |
The invoker knows only execute() and undo() |
New devices and actions don't change the invoker |
| Undo must restore what the command found | Record the old state in execute(); never guess an inverse |
| Chain of Responsibility: handlers linked in a list | The sender knows only the first handler; the first that accepts wins |
| The order and the end of the chain are design decisions | Put forwarding and the "unhandled" fallback in the base class |
| Memento: an opaque, immutable snapshot | Only the originator creates and reads it; the caretaker just stores it |
| Take a memento before each change | Otherwise the first undo does nothing |
6. Gotcha checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| The second undo redoes the first undo | only the last action is remembered | a stack of executed commands |
| Undo produces a state the user never had | undo() applies a guessed opposite |
record the previous state in execute() and restore it |
java.util.Stack in new code |
legacy class used for a stack | ArrayDeque through the Deque interface |
| A request silently disappears | no end-of-chain fallback, or it lives in one handler | base class forwards and reports unhandled requests |
| Requests reach the wrong handler | the chain's order was changed casually | build the chain in one place; document its order |
| The first undo does nothing | the memento was taken after the change | take it before each change, inside the operation |
| Restoring a memento brings back a later state | the memento shares a mutable list with the originator | copy or freeze mutable fields when saving |
| Undo history uses too much memory | a full snapshot per change, forever | cap the history, or store deltas |
✅ Check yourself
One check per objective. Answer before you open anything.
An online spreadsheet needs undo and redo for cell edits, a toolbar whose buttons can be reconfigured, and validation of every edit by a series of checks (type, range, permissions) that varies by sheet. Which patterns from this lesson fit, and how do they fit together?
Command for each edit: an EditCellCommand records the cell's old value in execute() and restores it in undo(); executed commands go on an undo stack, undone ones on a redo stack (cleared when a new edit is made). Toolbar buttons hold commands, so they can be reassigned. Chain of Responsibility for validation: TypeCheck, RangeCheck and PermissionCheck handlers, assembled per sheet, each rejecting the edit or passing it on, with the base class reporting the outcome at the end of the chain; the command executes only if the chain approves. Memento fits operations that are hard to reverse, such as "sort range" or "paste 10,000 cells": the command snapshots the affected range before running, and restores it on undo.
📚 Sources
- Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994), ch. 5 "Behavioral Patterns": Chain of Responsibility, Command, Memento.
java.util.Stackandjava.util.ArrayDeque, Java SE 21 API — https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Stack.html
🧪 Predict, then check.
- In the Command solution in section 1, press button 3 (AC off) before anything else, then undo. Predict both lines.
- In the same program, call
remote.setCommand(1, new LightOnCommand(light))before pressing any buttons. Predict what pressing button 1 and then undo print. - In the Chain of Responsibility solution in section 2, replace
technical.setNextHandler(delivery)withtechnical.setNextHandler(general), so the chain loops back to its start. Predict what an "unknown" request does. - In the Memento solution in section 3, call
history.undo(editor)a third time, followed byeditor.printResume(). Predict what is printed.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.