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