O — Open/Closed Principle: модуль должен быть открыт для расширения и закрыт для изменения. Иначе говоря, чтобы добавить поведение, не нужно править работающий и протестированный код. Любая правка может сломать старые случаи, а новый класс старых не трогает.

Слева правка функции, справа добавление нового класса
Слева правка функции, справа добавление нового класса

Нарушение: цепочка if/elif

Для каждого нового вида скидки приходится дописывать ветку в discount(). С ростом функции старые тесты оказываются под угрозой.

python
def discount(kind, price):
    if kind == "regular":
        return price
    elif kind == "student":
        return price * 0.9
    elif kind == "vip":
        return price * 0.8
    raise ValueError(f"unknown kind: {kind}")

for kind in ("regular", "student", "vip"):
    print(kind, discount(kind, 100))
try:
    discount("black-friday", 100)           # a new case: we must edit discount()
except ValueError as err:
    print(err)
text
regular 100
student 90.0
vip 80.0
unknown kind: black-friday

Способ 1: полиморфизм

Общий базовый класс (контракт) и подкласс для каждого случая. checkout() знает только контракт. Новый BlackFriday добавляется, и ничего не правится. Это строится на наследовании и ABC.