Что такое Liskov Substitution Principle и что обязан соблюдать подтип?
- Приведите пример нарушения LSP из стандартной библиотеки Java.
- Объясните Liskov Substitution Principle и приведите пример нарушения — желательно из стандартной библиотеки Java.
Подтип должен быть подставим вместо базового типа без сюрпризов для вызывающего: не усиливать предусловия, не ослаблять постусловия, сохранять инварианты, не бросать новых исключений, которых клиент не ждёт. Это про поведение, а не про компиляцию.
- Классика: Square extends Rectangle — setWidth меняет и высоту
- В JDK: Arrays.asList и List.of бросают UnsupportedOperationException на add
- Stack extends Vector открывает доступ по индексу
А ещё Признак нарушения — instanceof-проверки в клиентском коде
Подробно
Формулировка: если S — подтип T, то объекты T в программе можно заменить объектами S, и корректность не пострадает.
Правила контракта:
- предусловия нельзя усиливать — подтип не может требовать от вызывающего больше (принимать только положительные числа, когда базовый принимал любые);
- постусловия нельзя ослаблять — подтип не может гарантировать меньше;
- инварианты базового типа сохраняются;
- правило истории — подтип не меняет состояние способами, невозможными для базового (изменяемый подкласс неизменяемого типа).
Пример:
void resize(Rectangle r) {
r.setWidth(5); r.setHeight(4);
assert r.area() == 20; // для Square — 16
}
Квадрат математически — прямоугольник, но изменяемый квадрат не ведёт себя как изменяемый прямоугольник.
В JDK: List.add у Arrays.asList и List.of бросает UnsupportedOperationException — формально документировано как «необязательная операция», но клиент, получивший List, не может на add положиться.
Как избежать: наследовать только при настоящем «является» по поведению; предпочитать композицию; делать иерархии неизменяемыми.
Что хотят услышать
- Формулировка через поведение.
- Правила пред- и постусловий, инварианты.
- Пример нарушения, лучше из JDK.
Частые ошибки
- «Подтип должен переопределять все методы» — не относится к LSP.
Проверьте себя
Несколько верных вариантов. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Не усиливать предусловия — не требовать от вызывающего больше, чем базовый тип
- ✗Переопределять все методы базового типа и добавлять в них дополнительные проверки аргументов
- ✓Не ослаблять постусловия — не гарантировать меньше, чем базовый тип
- ✓Сохранять инварианты базового типа
Что спросят дальше
- Почему Square extends Rectangle нарушает LSP?
- Где LSP нарушает сам JDK?
- Как LSP связан с ISP?
- Почему Stack extends Vector в JDK считают ошибкой дизайна?
Разбор темы
- SOLID: пять принципов дизайна
SRP, OCP, LSP, ISP, DIP — что каждая буква значит на самом деле, как выглядит нарушение в Java-коде, чем DIP отличается от DI и IoC.
Связанные вопросы
- Почему java.util.Stack, унаследованный от Vector, считают ошибкой дизайна? Middle
- Что произойдёт при вызове add на списке, полученном из Arrays.asList(array)? Middle
- В коде расчёта комиссии есть метод с цепочкой if по типу платежа, и при добавлении каждого нового способа оплаты приходится править этот метод и ещё три похожих. Какой принцип SOLID нарушен в первую очередь? Middle
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.