Модуль 6 · Урок 23 із 58
Composition, inheritance і polymorphism
Повторне використання коду не означає автоматично inheritance. Спочатку визначте relationship: object є спеціалізованим різновидом base type чи лише використовує інший object як collaborator. Composition зазвичай робить залежності явними; inheritance доречна для стабільного substitutable contract.
Безпечна практика модуля 6
Пакет містить тільки synthetic records, validated value objects, composition, Protocol, deterministic summary і локальні tests. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.
Composition моделює has-a relationship
class CaseQueue:
def __init__(self, policy: "RoutingPolicy") -> None:
self._policy = policy
def route(self, case: "CaseRecord") -> str:
return self._policy.route(case)Queue не є policy; вона використовує policy. Collaborator передається явно, тому test може підставити просту fake implementation. Composition дозволяє змінювати behavior без fragile hierarchy і без доступу subclass до internal state.
Inheritance моделює is-a та substitutability
class BasePolicy:
def route(self, case: "CaseRecord") -> str:
return "standard"
class PriorityPolicy(BasePolicy):
def route(self, case: "CaseRecord") -> str:
if case.priority == 1:
return "urgent"
return super().route(case)Subclass override має виконувати contract base method: приймати сумісні inputs, повертати очікуваний type і не посилювати приховані preconditions. Якщо caller не може безпечно використати subclass там, де очікує base, hierarchy невдала.
super() продовжує cooperative method chain
super() не означає буквально «виклик parent». Він продовжує lookup за Method Resolution Order поточного class. Це важливо при multiple inheritance: hard-coded BasePolicy.route(self, case) може пропустити class у cooperative chain.
print(PriorityPolicy.mro())
# [PriorityPolicy, BasePolicy, object]Multiple inheritance варто застосовувати лише зі справді cooperative classes та узгодженими signatures. Для звичайного application design composition часто читабельніша.
Polymorphism потребує behavior, а не спільного ancestor
from typing import Protocol
class RoutingPolicy(Protocol):
def route(self, case: "CaseRecord") -> str: ...
def build_route(case: "CaseRecord", policy: RoutingPolicy) -> str:
return policy.route(case)Duck typing дозволяє object брати участь у operation, якщо він підтримує потрібний method. Protocol робить structural contract видимим для type checker без runtime inheritance. Runtime correctness усе одно доводять tests.
runtime_checkable має вузькі гарантії
@runtime_checkable дозволяє isinstance з protocol, але перевіряє лише presence attributes, не точні signatures або semantic behavior. Не використовуйте це як validation недовірених plugins чи security boundary.
Ознаки зайвої hierarchy
- Subclass успадковує багато methods, які не можна використовувати.
- Base class знає деталі всіх майбутніх variants.
- Behavior перемикається довгим набором
isinstance. - Tests мусять ініціалізувати unrelated parent state.
- Заміна маленького collaborator вирішує задачу простіше.
Definition of Done
- Relationship між types названий до вибору inheritance.
- Overrides зберігають documented base contract.
super()використано з розумінням MRO.- Collaborators передаються явно й можуть бути замінені у test.
- Protocol описує мінімальну потрібну behavior surface.
Методичні джерела
Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.
Закріпіть матеріал уроку
Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.