The Decorator pattern adds behavior to an object dynamically by wrapping it in another object with the same interface. The wrapper forwards the call to the wrapped object and does something before or after. Unlike inheritance, decorators can be stacked and chosen at runtime.
Why not subclasses?
A coffee can get any combination of add-ons. With inheritance each combination is a class: two bases and three add-ons already need 16, and every new add-on doubles the count.
from itertools import combinations
bases = ["espresso", "tea"]
addons = ["milk", "sugar", "cream"]
combos = [(base, extra)
for base in bases
for r in range(len(addons) + 1)
for extra in combinations(addons, r)]
print(len(combos)) # subclasses needed with inheritance
print(combos[:3])16
[('espresso', ()), ('espresso', ('milk',)), ('espresso', ('sugar',))]Structure
A decorator is a Beverage (implements the interface) and has a Beverage (holds inner). Each method delegates to inner and adds its part, so any number of decorators can be nested.
class Espresso:
def description(self):
return "espresso"
def cost(self):
return 200