Модуль 6 · Урок 23 із 58

Composition, inheritance і polymorphism

Повторне використання коду не означає автоматично inheritance. Спочатку визначте relationship: object є спеціалізованим різновидом base type чи лише використовує інший object як collaborator. Composition зазвичай робить залежності явними; inheritance доречна для стабільного substitutable contract.

compositioninheritancesuperProtocol

Безпечна практика модуля 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.

Relationshipis-a чи has-a?
ContractЧи subclass substitutable?
ChainЧи узгоджені super і MRO?
TestДві policy дають різний deterministic result.

Ознаки зайвої 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.

Практична перевірка · урок 23 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. CaseQueue використовує RoutingPolicy, але сама не є policy. Який relationship і design найточніші?
2. Коли inheritance PriorityPolicy(BasePolicy) є виправданою за contract, а не лише для reuse?
3. Override route() приймає менше допустимих inputs, ніж base contract. Яку проблему це створює?