Behavioral Patterns III: Command & Iterator

Design Patterns

Chapter 9 · Behavioral Patterns III: Command & Iterator

This course's last pair of patterns before the capstone. Command turns "do this action" into an object in its own right — so it can be queued, logged, or undone. Iterator turns "go through this collection" into an object too — so the collection's own internal storage never has to leak out to the code walking through it.

Command: A Request as a Standalone Object

Instead of a remote control button directly calling light.turn_on(), it holds a small object that knows how to execute() the action — and, crucially, how to undo() it.

class LightOnCommand: def __init__(self, light): self.light = light def execute(self): self.light.turn_on() def undo(self): self.light.turn_off() class LightOffCommand: def __init__(self, light): self.light = light def execute(self): self.light.turn_off() def undo(self): self.light.turn_on() class RemoteControl: def __init__(self): self.history = [] def press_button(self, command): command.execute() self.history.append(command) def press_undo(self): if self.history: self.history.pop().undo()
Verified directly — undo correctly reverses whichever command was pressed last, without RemoteControl knowing what any of them do
Pressing LightOnCommand then LightOffCommand leaves light.is_on at False. The first press_undo() reverses the last command pressed (the off command), bringing light.is_on back to True. A second press_undo() reverses the one before that (the on command), leaving light.is_on at False again. RemoteControl's own code never mentions "on" or "off" anywhere — it only ever calls .execute() and .undo().

Macro Commands: Composing Several Commands Into One

Because a command is just an object with execute()/undo(), a MacroCommand holding a list of other commands can implement the exact same interface — this is Chapter 5's Composite pattern, reapplied to commands instead of files and directories.

class MacroCommand: def __init__(self, commands): self.commands = commands def execute(self): for c in self.commands: c.execute() def undo(self): for c in reversed(self.commands): c.undo()
Verified directly — a macro's own undo runs its sub-commands in strictly reversed order
A MacroCommand([LightOnCommand(light), FanOnCommand(fan)]), when executed, turns on both the light and the fan, with a call log confirming the order ['Living Room Light ON', 'Ceiling Fan ON']. Calling undo() on that same macro produces the log entries ['Ceiling Fan OFF (undo)', 'Living Room Light OFF (undo)'] — the fan (turned on second) is undone first, and the light (turned on first) is undone last. RemoteControl.press_undo() triggered this whole reversed sequence with the exact same one-line call it uses for a single command.

Iterator: Traversing a Collection Without Exposing How It's Stored

A collection hands out a separate iterator object that knows how to walk through it — the calling code never touches the collection's own internal list, dict, or tree directly.

class BookShelf: # backed by a list def __init__(self): self._books = [] def add(self, book): self._books.append(book) def create_iterator(self): return BookShelfIterator(self._books) class BookShelfIterator: def __init__(self, books): self._books = books; self._index = 0 def has_next(self): return self._index < len(self._books) def next(self): book = self._books[self._index]; self._index += 1 return book class Playlist: # backed by a dict — genuinely different storage def __init__(self): self._songs = {} def add(self, song_id, title): self._songs[song_id] = title def create_iterator(self): return PlaylistIterator(list(self._songs.values()))
Verified directly — the identical client-facing loop works over two genuinely different storage types
A shared print_all(iterator) function (a plain while iterator.has_next(): ... loop) run against a BookShelf (internally a list) returns ['Book A', 'Book B', 'Book C']. The same print_all() function, unchanged, run against a Playlist (internally a dict) returns ['Song X', 'Song Y', 'Song Z']. Neither call ever touches _books or _songs directly — print_all() has no idea one collection is a list and the other is a dict, and doesn't need to.
Verified directly — two independent iterators over the same collection don't interfere with each other
Creating it1 from a shelf and advancing it twice ('Book A', 'Book B') leaves it partway through. Creating a second, brand-new it2 from the same shelf starts fresh from the beginning — its first call also returns 'Book A'. Advancing it1 again correctly continues from where it left off ('Book C'), leaving it1.has_next() as False while it2.has_next() is still True, having only consumed one item. Each iterator owns its own _index — the shelf itself was never asked to remember a position.

Where This Connects

This chapter's findingWhat it connects to
MacroCommand's own execute/undo list, verified running in forward and reversed orderChapter 5's Composite — a command composed of commands, exactly like a Directory composed of Files and Directories
Command wrapping a call behind an identical execute() interfaceStructurally close to Chapter 7's Strategy — but Command represents a specific request to be replayed or undone later, not a swappable algorithm to run immediately
Iterator's shared traversal interface over genuinely different storagePseudocode & Algorithmic Problem-Solving's own "the same algorithm, expressed independently of implementation" theme, applied to walking a collection specifically

Hands-On Exercises

Exercise 1

Build a MacroCommand that turns off both the light and the fan from this chapter's own example (using LightOffCommand/FanOffCommand, which you should also write, following the existing on-command shape). Press it, then undo it, and verify both the resulting device states and the call-log order at each step.

📄 View solution
Exercise 2

Add a third collection type, PlayingCardDeck, backed by a tuple of card names rather than a list or a dict, with its own iterator following this chapter's own has_next()/next() shape. Verify print_all() works on it unmodified.

📄 View solution
Exercise 3

Explain, using this chapter's own two verified patterns, why a MacroCommand's reversed-order undo is a genuine correctness requirement (not just a style choice), while the order two separate BookShelfIterator objects are created in has no such requirement at all.

📄 View solution

Chapter 9 Quick Reference

  • Command: a request becomes a standalone object with execute()/undo(), letting it be queued, logged, or reversed — verified: two consecutive undos correctly reversed a remote's last two button presses in order, and a MacroCommand's own undo ran its sub-commands in strictly reversed order (fan undone before light, after light was turned on before fan)
  • Iterator: a separate object handles traversal, so a collection's internal storage never has to be exposed — verified: the identical client loop walked a list-backed and a dict-backed collection with no changes, and two independent iterators over the same collection tracked their own positions without interfering
  • Next chapter: Capstone — refactoring a real, patternless codebase using several patterns together