O is the Open/Closed Principle: a module should be open for extension and closed for modification. In other words, adding behaviour should not require editing code that already works and is tested. Every edit can break old cases, while a new class leaves the old ones alone.
The violation: an if/elif chain
Every new kind of discount needs a new branch in discount(). As the function grows the old tests are at risk.
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)regular 100
student 90.0
vip 80.0
unknown kind: black-fridayWay 1: polymorphism
A common base class (the contract) and a subclass per case. checkout() knows only the contract. A new BlackFriday is added and nothing is edited. It builds on inheritance and ABC.
class Discount:
def apply(self, price):
return price