Получить доступ

Что такое Liskov Substitution Principle и что обязан соблюдать подтип?

MiddleПринципы проектирования
Ещё спрашивают так:
  • Приведите пример нарушения LSP из стандартной библиотеки Java.
  • Объясните Liskov Substitution Principle и приведите пример нарушения — желательно из стандартной библиотеки Java.
Коротко

Подтип должен быть подставим вместо базового типа без сюрпризов для вызывающего: не усиливать предусловия, не ослаблять постусловия, сохранять инварианты, не бросать новых исключений, которых клиент не ждёт. Это про поведение, а не про компиляцию.

  • Классика: Square extends Rectangle — setWidth меняет и высоту
  • В JDK: Arrays.asList и List.of бросают UnsupportedOperationException на add
  • Stack extends Vector открывает доступ по индексу

А ещё Признак нарушения — instanceof-проверки в клиентском коде

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Формулировка: если 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 считают ошибкой дизайна?

Разбор темы

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем