L is for Liskov — SOLID принципы знаете? Думаю, нет разработ | Java: fill the gaps
L is for Liskov
— SOLID принципы знаете?
Думаю, нет разработчика, который не слышал на собеседовании такой вопрос.
Расшифровку знают многие. А вот практические знания часто ограничиваются Single Responsibility и Interface Segregation.
Как применять остальные буквы в ежедневной работе — не всегда понятно. Больше всего вопросов возникает насчёт L — Liskov Substitution Principle. О нём и будет сегодняшний пост.
Полное определение звучит так:
Пусть q(x) является свойством верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.
По-простому: если заменить класс А на подкласс B, то система будет работать корректно и без неожиданных сайд-эффектов.
A service1 = new A();
A service2 = new B();
Для наблюдателя service1 и service2 ведут себя совершенно одинаково. Класс-наследник дополняет поведение родителя, а не замещает его. В результате система работает более предсказуемо.
Как это выглядит на практике:
Выходной тип метода в наследнике такой же как у родителя или расширенный
Базовый класс: Info getInfo()
Наследник:
BigInfo getInfo()
Object getInfo()
Подклассы не бросают дополнительных исключений, но могут уменьшить их список
Базовый класс: void save() throws FileNotFoundException
Наследник:
void save()
void save() throws FileNotFoundEx, InterruptedEx
Java — типизированный язык, поэтому пункты 1 и 2 контролируются компилятором.
Типы входных параметров те же или менее строгие. Пункт для общего понимания, тк для Java это неприменимо
Базовый класс: void add(Account acc)
Наследник:
void add(Object acc)
void add(AdminAccount acc)
Следующие пункты компилятор уже не проверит, это целиком ответственность программиста.
Метод подкласса делает то же, что и метод базового класса
Базовый класс: метод countVisitors считает пользователей
Наследник:
Считает пользователей чуть по-другому
Считает пользователей, обновляет статистику, сохраняет результат в БД
Метод наследника взаимодействует с теми же сущностями:
Метод родителя увеличивает счётчик - подкласс тоже увеличивает
Метод родителя не меняет поле - подкласс тоже не меняет
Метод родителя вызывает другие методы в определённом порядке - подкласс делает то же самое
А что можно вообще?
Если в подклассе объявлены новые поля, то методы подкласса могут делать с ними что угодно. На этом всё
Правила выше - очень строгие. Но и наследование — штука непростая, это самая сильная связь между сущностями. Часто единственный плюс — это краткость кода, но по ходу развития проекта ограничения доставляют всё больше проблем.
Нарушения принципа подстановки — повод пересмотреть иерархию наследования или совсем от неё отказаться.