И наследование, и композиция переиспользуют код, но связывают классы по-разному. Наследование (is-a) привязывает подкласс к внутренностям базового. Композиция (has-a) держит один объект внутри другого и использует только его публичный интерфейс. Основы — в уроке Композиция и наследование; этот урок — про проектное решение.
Penguin наследует Bird.fly и вынужден бросать ошибку; справа у Bird есть Mover, его реализуют Fly и WalkКогда наследование оправдано
- Настоящее is-a. Каждый подкласс выполняет все обещания базового класса, поэтому его можно подставить везде, где ждут базовый: принцип подстановки Лисков.
- Стабильная база, рассчитанная на расширение:
Exception, абстрактные базовые классы, точки расширения фреймворков вродеunittest.TestCase. - Неглубокое дерево: один-два уровня.
class Bird:
def fly(self):
return "flap"
class Sparrow(Bird):
pass
class Penguin(Bird):
def fly(self):
raise NotImplementedError("penguins cannot fly")
for b in [Sparrow(), Penguin()]:
try:
print(type(b).__name__, b.fly())
except NotImplementedError as e:
print(type(b).__name__, "fails:", e)Sparrow flap
Penguin fails: penguins cannot flyPenguin наследует fly(), который не может выполнить. Теперь каждому коду, обходящему объекты Bird, нужен особый случай. Если подкласс бросает NotImplementedError или молча ничего не делает в унаследованном методе, «is-a» было лишь иллюзией.