Примеры нарушения принципа подстановки Барбары Лисков в Android
Что проверяют
Отличаете ли вы нарушение контракта подтипа от обычного полиморфизма. Большинство пересказывает формулировку и приводит примеры, в которых нарушения нет, и это видно с первой фразы.
Как отвечать
Начните с сути: объект подтипа должен подставляться вместо базового так, чтобы код, написанный против базового типа, продолжал работать. Нарушение это не «наследник ведёт себя иначе», а «наследник ломает обещание базового типа»: сужает набор допустимых аргументов, ослабляет постусловие, бросает исключение там, где база его не бросала, или требует дополнительной инициализации.
Примеры из андроида:
- адаптер, у которого
getItemCount()возвращает одно число, аonBindViewHolderпадает на части позиций:RecyclerViewвправе запросить любую позицию из заявленного диапазона; - наследник
View, игнорирующий переданныйMeasureSpecи всегда возвращающий фиксированный размер: родительская раскладка рассчитывает на соблюдение контрактаonMeasure; - обёртка над списком, где
add()бросаетUnsupportedOperationException, как это делаетArrays.asList: код, принимающий изменяемый список, падает; - фрагмент, которому перед показом нужно вручную выставить поле, иначе
NullPointerException: у базовогоFragmentтакого предусловия нет, и система пересоздаст его сама после смены конфигурации.
Хорошо закончить лечением: композиция вместо наследования, разделение интерфейса на читающий и меняющий, классы, закрытые для наследования по умолчанию.
Где спотыкаются
Считают нарушением любое переопределение. Наследник, возвращающий другой набор прав, это как раз работающий полиморфизм. Нарушение началось бы, если бы метод вместо списка начал возвращать null или бросать исключение.
Приводят квадрат и прямоугольник, но не объясняют, в чём именно поломка. Она в постусловии: установка ширины у квадрата меняет и высоту, а вызывающий код рассчитывал, что высота осталась прежней. Для неизменяемых значений проблемы не возникает вовсе.
Предлагают как лечение проверку типа в вызывающем коде. Такая ветка означает, что подстановка уже сломана, и цена растёт с каждой новой реализацией: про новый подтип придётся вспомнить во всех местах, где стоит проверка.