Design Patterns with Functions
Source: Fluent Python, 2nd Edition — Chapter 10 by Luciano Ramalho
A design pattern is a known recipe for a recurring design problem. The Gang of Four catalog assumed C++/Smalltalk. In a language with first-class functions, several of those recipes shrink: you do not need a class whose only job is one method named execute or discount.
This chapter is not “use more patterns.” It is: start from Strategy and Command, then replace single-method objects with callables.
Strategy, in one paragraph
Strategy: a family of algorithms, each interchangeable, so the client can vary the algorithm without rewriting the context.
Ecommerce sketch — an order (the context) asks a promotion for a discount:
- 5% off the whole order if loyalty points ≥ 1000
- 10% off each line with quantity ≥ 20
- 7% off the whole order if there are ≥ 10 distinct products
The caller chooses one promotion (or a “best of” later). Classic OOP: ABC Promotion plus three subclasses with no instance state. That is a lot of types for three formulas.
🧪 A promo is just a function
Mini quiz — Strategy
Strategy
1 / 2In Strategy, the context (Order) should
Pick an answer to see if you are right.
Challenge: loyalty discount
promo(points, total) returns the discount in whole units: 5% of total (//) if points >= 1000, else 0.
Functions instead of promo classes
Pass the function in. If promotion is an instance attribute holding a callable, self.promotion(self) is not a method call — you retrieve the function, then pass the order. Bound methods get self for free; stored functions do not.
When a strategy does keep mutable state, keep a class or a closure. The GoF hint that Strategy objects make good flyweights is a hint you were paying for extra instances — a module-level function is already shared.
Mini quiz — callables as strategies
Callables as strategies
1 / 2order.promotion(order) mentions order twice because
Pick an answer to see if you are right.
Best of several functions
best_promo(order) is max(fn(order) for fn in promos). A list of functions is ordinary data.
How you fill promos | Risk |
|---|---|
| Typed list | Easy to forget a new function |
globals() + _promo suffix | Brittle names |
inspect on a module | Every function in that module is assumed to be a promo |
@promotion registration | Explicit; disable one promo by commenting @promotion |
Challenge: max over strategies
best(total, promos) must apply every function in promos to total and return the largest result. Calling only the first function fails a hidden test.
Registration decorator
Same idea as chapter 9: @promotion appends the function to a list and returns it. Then best cannot forget a promo unless you skip the decorator on purpose.
Challenge: register then best
Implement promotion so it records functions in promos. best(total) must use that list, not a hard-coded formula. The wrap will register a promo that is larger than “10%”.
Command as a callback
Command puts an object between “menu clicked” (invoker) and “document changed” (receiver), with an execute() method. Gamma et al. even call commands an OO stand-in for callbacks.
In Python, the invoker can store a function and call command(). A macro is a callable that holds a list of callables:
class Macro:
def __init__(self, commands):
self.commands = list(commands)
def __call__(self):
for c in self.commands:
c()
Undo and extra state may still want a class with more than __call__, or a closure. The smell to watch: an interface with one method named run / do_it / execute — often a function would do.
Mini quiz — Command
Command
1 / 2A Python callable already implements
Pick an answer to see if you are right.
Challenge: run a macro
Macro(cmds) must copy the list. Calling the instance runs each command. Each command is a function of no args that appends a letter to a shared list log in the wrap. Print ''.join(log).
Chapter summary
- Strategy plugs an algorithm into a context; in Python that plug is often a function.
self.promo(self)is a stored callable, not a bound method.bestismaxover a list of functions; keep that list honest with a decorator (or accept the maintenance hazard).- Command is often a callback;
__call__plus a list gives macros. - First-class functions make some GoF patterns shorter — not every pattern, and not when the strategy is stateful.
Exercises
Each input pair is qty price (integers). Discount is 10% (// 10) of qty * price for every line with qty >= 20, summed. Lines under 20 add 0.