Так сформулировал вопрос один из студентов нашего курса. Хочу ответить публично.
Говоря простым языком, если класс A использует (зависит) от интерфейса или Абстрактного класса X, то ему глубоко фиолетово какая именно имплементацию X используется: Z, Y, Я или 大. Более того, мы можем подменять одну имплементацию другой и А (как и вся программа) о подмене узнать не должен. Но ведь это и есть наследованные скажите вы? Да, если мы применяем LSP к ООП, то наследование является необходимым, но недостаточным условием соблюдение LSP.
Почему недостаточным? Например, вот что можно написать в X
Class A {
val X
fun hello() {
if (X instance of Z) { // Упс….
then ….
}
}
Вот в этот момент Liskov Substitution Principle нарушился. Теперь класс A знает что если передана имплементацию Z, то нужно делать что-то особенное. И даже если вы создадите имплементацию точно повторяющую Z, но называющаяся Y, то поведение программы не будет прежним.
Другой пример:
A знает что вызов X приведет к изменению глобального состояния и этим пользуется. Но при том в интерфейсе не гарантировано, что это изменение обязано произойти. И следовательно другие имплементации X могут не менять глобальное состояние.
К примеру
Class A {
val X
fun hello() {
x.calculateTotal();
// упс
db.read(“SELECT total from table”)
}
}
Некоторые имплементацию X могут действительно писать в базу данных, но интерфейс не обзывает их это делать.
LSP, Как и другие SOLID принципы применим не только к классам. Его можно применять так же
- К лямбдам
- К модулям и пакетам (в java скажем)
- Да и к микросервисам.
Кстати на Вот Здесь мы подробно разбираем SOLID.
А вы встречали более изощренные нарушения LSP?
P.S. На КДПВ сама Барбара Лисков 😍
