Ответ
Изменение публичного интерфейса (например, сигнатуры метода) в классе-наследнике является прямым нарушением Принципа подстановки Барбары Лисков (LSP), который тесно связан с Принципом открытости/закрытости (OCP).
OCP гласит, что сущности должны быть открыты для расширения, но закрыты для изменения. Когда наследник меняет контракт родителя, любой код, рассчитанный на работу с базовым классом, сломается при передаче ему экземпляра класса-наследника. Это заставляет изменять существующий код, что нарушает OCP.
Неправильный подход (Нарушение LSP и OCP)
class Notifier:
def send(self, message: str):
print(f"Sending: {message}")
class EmailNotifier(Notifier):
# Нарушение: изменена сигнатура, добавлен обязательный параметр `recipient`
def send(self, message: str, recipient: str):
print(f"Sending email to {recipient}: {message}")
# Этот код сломается, если notifier будет экземпляром EmailNotifier
def broadcast(notifier: Notifier, message: str):
notifier.send(message) # TypeError: send() missing 1 required positional argument: 'recipient'
Правильный подход (Соблюдение принципов)
Наследник должен расширять функциональность, не изменяя существующий контракт. Новые параметры следует делать необязательными.
class Notifier:
def send(self, message: str, **kwargs):
print(f"Sending: {message}")
class EmailNotifier(Notifier):
# Расширение: используется опциональный параметр
def send(self, message: str, **kwargs):
recipient = kwargs.get('recipient')
if recipient:
print(f"Sending email to {recipient}: {message}")
else:
super().send(message, **kwargs)
# Этот код будет работать с любым наследником Notifier
def broadcast(notifier: Notifier, message: str):
notifier.send(message)
Ключевая идея: наследник должен полностью соответствовать контракту родителя, чтобы его можно было безопасно подставить вместо родительского объекта.