Skip to Content

Заключение

Что важно запомнить о LSP

  1. LSP — это про контракт целиком, а не только про сигнатуру.
    Предусловия, постусловия и инварианты — вот что составляет контракт. Наследник не может усиливать предусловия (требовать больше, чем родитель), не может ослаблять постусловия (гарантировать меньше), не может нарушать инварианты. Сигнатура может остаться корректной — а контракт при этом нарушенным.

  2. Главный признак нарушения — разное поведение при одинаковых условиях.
    Если три реализации одного интерфейса при одном и том же входе ведут себя по-разному (одна молчит, две падают; одна бросает KeyNotFoundException, другая ничего не делает) — это сигнал. Клиент не может писать общий код, потому что не знает, чего ожидать от конкретной реализации.

  3. Инструменты LSP — шаблонный метод, защитные проверки, единые стратегии.
    Вместо того чтобы давать наследнику самому решать, что делать с предусловием, вынесите проверку в базовый класс. Вместо ! — явная проверка. Вместо разных стратегий для «не найдено» — одна общая. Чем меньше у наследника свободы «сломать контракт», тем лучше.

  4. Самое коварное — ошибка не проявляется сразу.
    Пока реализация одна, нарушение может спать годами. Первый же вызов метода в неподходящем состоянии, первая же вторая реализация интерфейса, первый же тест с моком — и контракт разваливается. Компилятор этого не поймает: C# и TypeScript проверяют сигнатуры, а не поведение. Ловить нужно тестами на подстановку — один тест, все реализации.

  5. NotImplementedException — это не нарушение LSP, а WIP.
    Незавершённый код не анализируется принципом подстановки. Метод, который ещё не написан, ничего не нарушает — он просто не написан. Другое дело — осознанный отказ: если метод остался в интерфейсе «на будущее» и никогда не будет реализован, это уже LSP-нарушение. Различайте эти два состояния.

  6. LSP помогает другим принципам SOLID.

    • OCP (Open-Closed): расширение через наследников возможно только тогда, когда наследники корректно подставляются вместо родителя. Без LSP OCP превращается в лотерею.
    • ISP (Interface Segregation): узкие интерфейсы проще соблюсти, чем «толстые». Если интерфейс обещает пять методов, а реализация выполняет три — это и ISP-проблема, и LSP-проблема.
    • DIP (Dependency Inversion): подстановка абстракции вместо конкретики работает только тогда, когда конкретика соблюдает контракт абстракции. DI-контейнер — техническая точка LSP.

Помните: LSP — это не про иерархии классов и не про extends. Это про любую подстановку: интерфейс + реализация, базовый класс + наследник, абстракция + конкретика. Если клиент не может отличить одну реализацию от другой по поведению — LSP соблюдён. Если может — нарушен. И самое главное: LSP — это не про идеальный код с первого раза. Это про осознанность. Когда вы понимаете, что обещает контракт, вы можете спроектировать наследников так, чтобы они это обещание не ломали.


Теперь, когда вы разобрали четыре реальных примера и прошли чек-лист, вы готовы применять LSP в своих проектах. Переходите к следующему принципу — Interface Segregation (ISP).

Last updated on