Skip to main content

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​

Quiz

Strategy

1 / 2

In 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​

Quiz

Callables as strategies

1 / 2

order.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 promosRisk
Typed listEasy to forget a new function
globals() + _promo suffixBrittle names
inspect on a moduleEvery function in that module is assumed to be a promo
@promotion registrationExplicit; 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​

Quiz

Command

1 / 2

A 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​

Key takeaways
  1. Strategy plugs an algorithm into a context; in Python that plug is often a function.
  2. self.promo(self) is a stored callable, not a bound method.
  3. best is max over a list of functions; keep that list honest with a decorator (or accept the maintenance hazard).
  4. Command is often a callback; __call__ plus a list gives macros.
  5. First-class functions make some GoF patterns shorter — not every pattern, and not when the strategy is stateful.

Exercises​

Activity: Bulk line discount

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.