Что такое принцип подстановки Лисков из SOLID?
Что проверяют
Знаете ли вы букву L в SOLID по смыслу, а не по месту в аббревиатуре. В Android наследование неизбежно: вы расширяете Activity, Fragment, View, адаптер списка, и каждый базовый класс приходит со своим негласным контрактом. Интервьюер проверяет, понимаете ли вы, что нарушение этого контракта даёт не ошибку компиляции, а падение у пользователя.
Как отвечать
Смысл: там, где код работает с базовым типом, замена на наследника не должна менять корректность. Отсюда требования к наследнику: не сужать допустимые входные данные, не давать результат слабее обещанного, сохранять инварианты базы, не выбрасывать новых исключений.
Как это выглядит в Android:
class ProfileFragment : Fragment() {
override fun onDestroyView() {
binding = null
}
}Пропущенный вызов super ломает контракт базового класса: фреймворк рассчитывает сделать свою часть работы и теряет состояние. То же с наследником View, который переопределяет onMeasure и игнорирует переданные спецификации: родительский layout получает размеры, о которых не просил.
Хороший ход в конце: Kotlin по умолчанию делает классы final, и это не мелочь синтаксиса, а способ не открывать наследование там, где контракт не продуман.
Где спотыкаются
Называют LSP вторым принципом SOLID. Он третий, и такая мелочь портит впечатление именно на вопросе про аббревиатуру.
Ограничиваются примером про пингвина. Вопрос от Android-разработчика ждёт своего материала: адаптер, который на части типов элементов возвращает из создания держателя не то, что обещал базовый класс, ломает контракт ровно так же.
Пытаются исправить нарушение проверкой типа перед вызовом. Ветка по классу наследника означает, что вызывающий код должен знать всех потомков, и следующий добавленный наследник сломает его молча.