Паттерн Декоратор динамически добавляет объекту поведение, оборачивая его другим объектом с тем же интерфейсом. Обёртка пересылает вызов оборачиваемому объекту и делает что-то до или после. В отличие от наследования, декораторы можно складывать стопкой и выбирать во время работы.
Почему не подклассы?
Кофе можно дополнить любой комбинацией добавок. При наследовании каждая комбинация — класс: две основы и три добавки уже требуют 16, а каждая новая добавка удваивает число.
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',))]Структура
Espresso, Milk и Sugar реализуют Beverage; декораторы хранят внутренний BeverageДекоратор является Beverage (реализует интерфейс) и имеет Beverage (хранит inner). Каждый метод делегирует inner и добавляет свою часть, поэтому декораторов можно вложить сколько угодно.
class Espresso:
def description(self):
return "espresso"
def cost(self):
return 200