description |
---|
Принцип подстановки Барбары Лисков | Liskov Substitution Principle | LSP |
Наследующий класс должен дополнять (extend — расширять), а не изменять базовый.
Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом.
Tип S будет подтипом Т тогда и только тогда, когда каждому объекту oS типа S соответствует некий объект objectT типа T таким образом, что для всех программ P, реализованных в терминах T, поведение P не будет меняться, если objectT заменить на oS.
// ExpensiveCar extends Car
// 1. Написали программу с классом Car
// 2. Заменили Car на ExpensiveCar
// 3. Ни один метод не сломался? Ура! LSP соблюден!!!
// Подтипы должны быть заменяемы их исходными типами.
let car = new Car();
car.drive();
car.stop();
car = new ExpensiveCar();
car.drive();
car.stop();
Более простыми словами можно сказать, что поведение наследуемых классов не должно противоречить поведению, заданному базовым классом, то есть поведение наследуемых классов должно быть ожидаемым для кода, использующего переменную базового типа.
// Представим, что мы имеем объект прямоугольник
class Rectangle {
setWidth (val) {
this.width = val;
}
setHeight (val) {
this.height = val;
}
getArea () {
this.width * this.height;
}
}
/*
* Позже нам понадобился объект квадрат.
* Основываясь на том, что квадрат является частным случаем прямоугольника,
* стороны которого равны, мы решили создать объект квадрат
* и использовать его вместо прямоугольника.
* */
class Square extends Rectangle {
setSize (val) {
this.size = val;
}
getArea () {
this.size * this.size;
}
}
/*
Проблема в том, что объект квадрат не может использоваться
в любом коде вместо прямоугольника.
*/
let figure = new Rectangle();
figure.setHeight(2);
figure.setWidth(3);
figure.getArea(); // 6
figure = new Square();
figure.setHeight(2);
figure.setWidth(figure.height);
figure.getArea(); // undefined (suddenly!)
Изначально LSP рассматривался как руководство по использованию наследования в ООП. Но со временем принцип приобрел более широкое применение - как принцип проектирования программных систем. Он стал распространяться на интерфейсы и реализации.
{% hint style="info" %} Существуют пользователи, которые зависят от четкого определения интерфейсов и взаимозаменяемости реализаций этих интерфейсов. {% endhint %}