Behavioral Patterns
18 min
Behavioral patterns are about how objects communicate and share responsibility. The Strategy pattern makes an algorithm swappable: instead of hardcoding ONE way to do something, a class holds a reference to a function (or object) that defines the behavior, and calls it generically:
class Discounter:
def __init__(self, strategy):
self.strategy = strategy # a function, chosen by the caller
def apply(self, price):
return self.strategy(price)
ten_percent_off = Discounter(lambda p: p * 0.9)
flat_ten_off = Discounter(lambda p: p - 10)
print(ten_percent_off.apply(100)) # 90.0
print(flat_ten_off.apply(100)) # 90
Same Discounter class, completely different discount logic — swapped
in from outside, with zero changes to Discounter itself. That's
exactly the same idea as the Strategy pattern in the Optimization
course's gradient descent functions accepting f_prime/grad_f as
arguments.
Adding a new discount strategy never requires touching the Discounter class -- you just define a new function and pass it in. That's the flexibility Strategy buys you.
The Observer pattern lets one object (the "subject") notify a list of other objects ("observers") whenever something happens, without needing to know anything about them beyond "they're callable":
class EventEmitter:
def __init__(self):
self.subscribers = []
def subscribe(self, callback):
self.subscribers.append(callback)
return self
def emit(self, value):
return [cb(value) for cb in self.subscribers]
emitter = EventEmitter()
emitter.subscribe(lambda x: print(f"logger: {x}"))
emitter.subscribe(lambda x: x * 2)
emitter.emit(21)
This is the exact pattern behind UI event listeners, pub/sub messaging systems, and reactive frameworks — the emitter doesn't know or care what its subscribers DO, it just calls them all when something happens.
Write `Discounter.apply(self, price)` (the Strategy pattern): `self.strategy` is a function; call it with `price` and return the result.
Write `EventEmitter.emit(self, value)` (the Observer pattern): call every subscribed callback in `self.subscribers` with `value`, and return a list of their return values, in subscription order.
What problem does the Strategy pattern solve?
You can implement the Strategy pattern to make behavior swappable at runtime, and the Observer pattern to notify multiple subscribers of an event.