Command, Iterator, and Visitor Patterns in the Lexi Document Editor
This section of the GoF case study addresses three design challenges in the Lexi editor: decoupling user operations from UI widgets (Command pattern), traversing heterogeneous glyph structures without exposing internal data structures (Iterator pattern), and performing diverse analyses on glyph structures without polluting the Glyph interface (Visitor pattern via double dispatch).
Key Concepts
Command pattern: Encapsulates a request as an object, decoupling the invoker (MenuItem, button, keyboard accelerator) from the operation's implementation. A Command abstract class defines Execute(), and subclasses implement specific operations.
Parameterizing with objects, not functions: Functions can't carry state, support undo, or be extended via inheritance. Command objects solve all three problems.
Undo/Redo via Command: Add Unexecute() to reverse Execute(), and Reversible() to determine at runtime whether a command is worth undoing (e.g., a no-op font change shouldn't consume an undo slot).
Command History: A linear list of executed commands with a "present" pointer. Undo moves the pointer left (calling Unexecute), redo moves it right (calling Execute). Supports arbitrary levels of undo.
Iterator pattern: Encapsulates traversal of a composite structure in a separate object, hiding the internal data structure (array, linked list, etc.) from clients.
Factory method for iterators: Glyph::CreateIterator() returns the appropriate iterator subclass. Leaf glyphs return a NullIterator (whose IsDone() always returns true).
Composite iterators: PreorderIterator uses a stack of glyph-specific iterators to traverse the entire tree, delegating to each glyph's own CreateIterator().
Separation of traversal from action: Iterators handle traversal; analysis objects handle what to do at each node. This allows reusing the same traversal (e.g., preorder) for different analyses (spelling, hyphenation, word count).
Double dispatch (Visitor precursor): Instead of type-testing glyphs, each glyph implements CheckMe(SpellingChecker&) which calls back checker.CheckGlyphSubclass(this). The specific glyph type is resolved by the virtual call to CheckMe, then the specific analysis is resolved by the call back to the checker — two dispatches.
Avoiding type tests: The dynamic_cast chain approach is fragile, hard to extend, and defeats the purpose of OO. Double dispatch eliminates it entirely.
Command pattern (p. 233): Decouples request issuers (UI widgets) from request implementors. Connects to the Composite pattern when menus contain MenuItems containing Commands.
Iterator pattern (p. 257): Works naturally with Composite structures. Factory Method (CreateIterator) lets each composite node produce the right iterator without exposing its storage.
Visitor pattern (emerging via double dispatch): The CheckMe/CheckGlyphSubclass technique is the core mechanism of Visitor. Separates algorithms from the object structure they operate on.
Bridge pattern (from prior section): Window/WindowImp separation mentioned as context — the Bridge lets logical window abstractions and platform implementations evolve independently.
NullIterator is an instance of the Null Object pattern — a degenerate iterator for leaf glyphs.
Exam-Relevant Points
Why objects over functions for commands: Functions can't carry state, support undo, or be extended. This is a key justification for the Command pattern.
Reversible() resolves a subtle problem: No-op commands (e.g., font change to the same font) should not consume undo slots. Runtime determination of undoability prevents meaningless undo stacking.
Command history is a list with a cursor, not a stack. This enables both undo and redo with arbitrary depth.
CreateIterator() is a Factory Method — each glyph subclass decides which iterator to return based on its internal data structure.
PreorderIterator maintains a stack of iterators, not a stack of glyphs. Each level of the tree produces its own iterator via CreateIterator().
Double dispatch eliminates type-switching: The glyph knows its own type (first dispatch via virtual CheckMe), then calls back the analyzer with this as the correct type (second dispatch). This is the mechanism underlying the Visitor pattern.
"Encapsulate the concept that varies" is the recurring design principle: requests vary → Command; traversal varies → Iterator; analysis varies → Visitor.
Traversal and action are orthogonal concerns: The same preorder iterator serves spelling checking, hyphenation, word counting, and search. Combining them would force duplication.