Привет, с вами Катя, Flutter Dev Friflex. В предыдущем посте я рассказывала о принципе открытости/закрытости (O). Сегодня продолжаем серию и разбираем третью букву — L (Liskov Substitution Principle), принцип подстановки Барбары Лисков.
Что такое Liskov Substitution Principle?
Принцип подстановки Лисков гласит: объекты подклассов должны быть взаимозаменяемы с объектами их базового класса без нарушения корректности работы программы. Другими словами, если класс B наследуется от класса A, то в любом месте программы, где используется класс A, можно безопасно подставить класс B, и программа продолжит работать корректно.
Почему это важно?
Нарушение LSP приводит к непредсказуемому поведению программы. Код начинает проверять типы объектов с помощью if/else или is, что противоречит принципу открытости/закрытости и делает систему хрупкой. Соблюдение LSP гарантирует, что полиморфизм работает правильно и подклассы действительно являются специализацией базового класса.
Пример нарушения LSP:
❌Плохо:
class Bird {
void fly() {
print('Flying');
}
}
class Penguin extends Bird {
@override
void fly() {
throw Exception('Cannot fly'); // Нарушение LSP
}
}Здесь функция, принимающая Bird, ожидает, что любой потомок сможет летать. Но при подстановке пингвина код валится с ошибкой, значит, иерархия нарушает принцип Лисков.
✅Хорошо:
abstract class Bird {
void move();
}
class Sparrow extends Bird {
@override
void move() {
print('Flying');
}
}
class Penguin extends Bird {
@override
void move() {
print('Swimming');
}
}Ключевые правила LSP:
✔️Предусловия не могут быть усилены в подклассе — подкласс не должен требовать больше, чем базовый класс
✔️Постусловия не могут быть ослаблены в подклассе — подкласс должен гарантировать как минимум то, что гарантирует базовый класс
✔️Инварианты должны сохраняться — свойства, которые истинны для базового класса, должны оставаться истинными для подклассов
✔️Исключения — подкласс не должен выбрасывать новые типы исключений, которые не ожидаются от базового класса
Принцип подстановки Лисков — это основа правильного полиморфизма. Подклассы должны расширять поведение базового класса, не ломая ожидания клиентского кода. Если вы не можете безопасно подставить подкласс вместо базового класса, значит, наследование используется неправильно — подумайте о композиции или другой абстракции.
В следующем посте я расскажу о принципе разделения интерфейсов (I в SOLID).
А как вы работаете с наследованием в своих проектах? Сталкивались ли с нарушениями LSP? Делитесь в комментариях своим опытом применения принципа подстановки Лисков
