A weather station's Temperature object must update a phone display, a web widget, and a CSV logger whenever it reads a new value — and users can add or remove displays while the program runs. Which behavioral pattern fits, and what are the two roles involved?
Recall Solution
Pattern: Observer.
The Temperature object is the Subject: it keeps a list of listeners and calls notify() on change.
Each display/logger is an Observer: it implements a common update(data) method.
Why not the others? The key phrase is "one thing changes → many things react automatically, added/removed at runtime". That fan-out is Observer's exact signature. See Publish-Subscribe & Event-Driven Architecture for the same idea at network scale.
A checkout page can charge via credit card, PayPal, or crypto. The rest of the code should not contain if method == "paypal": .... Which pattern, and what does the client choose?
Recall Solution
Pattern: Strategy.
The checkout is the Context; each payment algorithm is a Strategy implementing one pay(amount) interface.
The client picks which strategy object to hand in; the context never branches on the method name.
A text editor needs Ctrl-Z. Each user action must be storable so it can be reversed later. Which pattern turns "actions" into storable things?
Recall Solution
Pattern: Command.
Each action becomes an object with execute() and undo(). A history list stacks them, so undo() pops and reverses. This is exactly Undo-Redo Systems.
Write a minimal Observer in Python: a Bell subject that lets observers subscribe, and notifies them with a string. Show that a Student observer prints Ding for <name>. Add one student, ring, and give the printed output.
Recall Solution
class Bell: def __init__(self): self._observers = [] # subject owns the list def subscribe(self, o): self._observers.append(o) def ring(self, msg): for o in self._observers: # decoupled fan-out o.update(msg)class Student: def __init__(self, name): self.name = name def update(self, msg): print(f"Ding for {self.name}: {msg}")b = Bell(); b.subscribe(Student("Ana"))b.ring("lunch")
Output:Ding for Ana: lunch
Note ring returns nothing — Observer is push, the subject does not collect answers.
Using Strategy, build a Calculator context whose behavior is a function passed in. Call it with an "add" strategy lambda a, b: a + b on inputs (4, 6), then swap to a "multiply" strategy lambda a, b: a * b on the same inputs. Give both results.
Recall Solution
class Calculator: def __init__(self, strategy): self.strategy = strategy def run(self, a, b): return self.strategy(a, b) # delegate, no if/elseprint(Calculator(lambda a, b: a + b).run(4, 6)) # 10print(Calculator(lambda a, b: a * b).run(4, 6)) # 24
Results:10 then 24. In Python a plain function is a strategy — no class hierarchy needed.
Using Command, implement AddNumber on a list doc, with execute() appending and undo() popping. Run three commands adding 10, 20, 30, then undo once. What is doc, and what is its sum?
Recall Solution
class AddNumber: def __init__(self, doc, n): self.doc, self.n = doc, n def execute(self): self.doc.append(self.n) def undo(self): self.doc.pop()doc, history = [], []def run(cmd): cmd.execute(); history.append(cmd)def undo(): history.pop().undo()for n in (10, 20, 30): run(AddNumber(doc, n))undo()
After the three runs doc == [10, 20, 30]; one undo() pops the last, giving doc == [10, 20], sum 30.
Strategy and State share the same UML: a Context holding an interface reference. State three concrete differences that let you tell them apart in real code.
Recall Solution
Who chooses the object. Strategy: the client injects it. State: the object often creates its successor.
Knowledge of siblings. Strategy objects are independent and ignorant of each other. State objects know the next state and trigger the switch.
Frequency of change. A Strategy is usually set once and stays. A State changes repeatedly during the context's life (Draft → Moderation → Published).
Structure is identical; intent distinguishes them (see Polymorphism — both lean on it, differently).
class Sorter: def sort(self, data, mode): if mode == "asc": return sorted(data) elif mode == "desc": return sorted(data, reverse=True)
Explain why this is not Strategy, and rewrite it as true Strategy. Which SOLID principle does the original violate?
Recall Solution
It is not Strategy — the algorithms are hard-coded inside an if/elif, so adding a "shuffle" mode means editingSorter. That violates the Open/Closed Principle (open for extension, closed for modification).
True Strategy:
In Chain of Responsibility, a request passes through handlers L1 → L2 → Manager. A ticket of severity 5 needs a handler whose max_level >= 5. Given L1.max=2, L2.max=4, Manager.max=10, trace which handler resolves it and why the earlier two pass it on.
Manager (max=10): 5 <= 10 → handles it.
The chain stops at the first handler able to cope. Each handler only knows its successor, never the whole chain — that is the decoupling Chain of Responsibility buys.
Design a smart thermostat that (a) lets the current mode — Eco, Comfort, Away — decide the target temperature, and (b) notifies a display and a logger whenever the target changes. Which two patterns combine, and which role plays which? Sketch the classes.
Recall Solution
Combine State (the mode decides behavior and the next mode) with Observer (target-change fan-out).
Thermostat is the Contextand the Subject.
Eco/Comfort/Away are State objects returning a target temperature.
Display/Logger are Observers.
class Eco: target = 18class Comfort: target = 23class Thermostat: def __init__(self): self.state = Eco(); self._obs = [] def subscribe(self, o): self._obs.append(o) def set_mode(self, state): self.state = state for o in self._obs: # Observer fan-out o.update(self.state.target)
Setting the mode changes State; the resulting target-change triggers Observer notification. Each pattern owns one axis of change.
Extend the editor from 2.3 with Command + Iterator: after running commands adding 10, 20, 30, iterate the history to replay every command onto a fresh empty list. What does the replayed list contain?
Recall Solution
fresh = []for cmd in history: # Iterator: for-loop over the command stack AddNumber(fresh, cmd.n).execute()
Iterating history (Iterator) and calling each stored execute() (Command) rebuilds the sequence: fresh == [10, 20, 30], sum 60. Because commands are first-class objects, they can be stored, iterated, and replayed — that is what makes macros and redo possible.
A document workflow is Draft → Moderation → Published, and only from Published can you go to Archived. Implement it with State so that calling publish() on an already-Published doc is a safe no-op, and calling archive() on a Draft is rejected. Trace the state after: d.publish(); d.publish(); d.archive().
d.publish() → Draft.publish → state becomes Moderation.
d.publish() → Moderation.publish → state becomes Published.
d.archive() → Published.archive → state becomes Archived.
Final state: Archived. Every illegal transition is handled by the state that owns it, so no scattered if checks exist — adding a new status is adding a class.
You must add "count the words" and "spell-check" operations to a class hierarchy of document nodes (Paragraph, Image, Table) — without editing those node classes, because they ship in a locked library. Which pattern, why, and what is the trade-off you accept?
Recall Solution
Pattern: Visitor.
Each node accepts a Visitor via accept(visitor), which calls back visitor.visitParagraph(self) etc. New operations = new Visitor classes; the node classes stay untouched (respects Open/Closed for operations).
Trade-off: Visitor makes adding new operations cheap but adding a new node type expensive — every existing visitor must gain a visitNewNode method. So Visitor is right precisely when the node hierarchy is stable and operations grow. If it were the reverse (nodes change often), Visitor would be the wrong tool.
Recall Self-test recap
Symptom "one change, many reactors, runtime add/remove" ::: Observer
Symptom "one task, interchangeable algorithms chosen by client" ::: Strategy
Symptom "actions must be stored, queued, undone" ::: Command
Symptom "behavior changes as internal status changes, states know successors" ::: State
Symptom "request tried by handlers until one copes" ::: Chain of Responsibility
Symptom "add operations to a fixed, locked class hierarchy" ::: Visitor
Structure of Strategy and State is identical; what distinguishes them ::: intent (who chooses / do objects know their successor)