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