Паттерн Декоратор динамически добавляет объекту поведение, оборачивая его другим объектом с тем же интерфейсом. Обёртка пересылает вызов оборачиваемому объекту и делает что-то до или после. В отличие от наследования, декораторы можно складывать стопкой и выбирать во время работы.

Почему не подклассы?

Кофе можно дополнить любой комбинацией добавок. При наследовании каждая комбинация — класс: две основы и три добавки уже требуют 16, а каждая новая добавка удваивает число.

python
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])
text
16
[('espresso', ()), ('espresso', ('milk',)), ('espresso', ('sugar',))]

Структура

`Espresso`, `Milk` и `Sugar` реализуют `Beverage`; декораторы хранят внутренний `Beverage`
Espresso, Milk и Sugar реализуют Beverage; декораторы хранят внутренний Beverage

Декоратор является Beverage (реализует интерфейс) и имеет Beverage (хранит inner). Каждый метод делегирует inner и добавляет свою часть, поэтому декораторов можно вложить сколько угодно.

python
class Espresso:
    def description(self):
        return "espresso"

    def cost(self):
        return 200