Что такое принцип подстановки Лисков (Liskov Substitution Principle)
Что проверяют
Понимаете ли вы, что речь о поведении, а не о совпадении сигнатур.
Как отвечать
Формулировка: объект базового типа должен заменяться объектом наследника без нарушения корректности программы. Вызывающий код, работающий с базовым типом, не должен ломаться, получив наследника.
Компилятор проверяет только сигнатуры. Принцип же про контракт поведения, и его формальные правила такие:
- предусловия не усиливаются: наследник не может требовать больше, чем родитель. Если родитель принимал любое число, наследник не вправе требовать только положительные;
- постусловия не ослабляются: наследник обязан гарантировать не меньше. Если родитель обещал непустой список, наследник не может вернуть пустой;
- инварианты сохраняются;
- новых исключений не появляется.
Примеры нарушения:
Квадрат и прямоугольник. Кажется естественным унаследовать первый от второго. Но вызывающий код, установивший ширину и высоту по отдельности и ожидающий соответствующую площадь, получит неверный результат: у квадрата установка одной стороны меняет обе. Отношение «является» в математике не означает совместимость по поведению.
Метод, бросающий исключение о неподдерживаемой операции. Наследник объявляет метод, но отказывается его выполнять. Неизменяемая коллекция, унаследованная от изменяемой, ломает любой код, который просто добавляет элемент.
Сужение допустимых входов: родитель работал с пустым значением, наследник на нём падает.
Простой признак нарушения на практике: вызывающий код начинает проверять конкретный тип. Появление проверок вида «а если это на самом деле такой наследник» означает, что подстановка не работает и абстракция выбрана неверно.
Что делать вместо: не наследовать, а выделить общий интерфейс с честным контрактом либо использовать композицию.
Где спотыкаются
Проверяют только совпадение сигнатур.
Бросают исключение о неподдерживаемой операции в наследнике.
Строят иерархию по сходству названий, а не по совместимости поведения.
Не видят связи между нарушением принципа и проверками типа в вызывающем коде.