Подстановочный принцип Барборы Лисков
Введение
Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) является одним из пяти принципов SOLID в объектно-ориентированном программировании. Он был сформулирован Барбарой Лисков в 1987 году и гласит, что объекты подклассов должны быть взаимозаменяемы с объектами их базовых классов.
Основные концепции
Определение LSP
Принцип подстановки Лисков гласит:
“Функции, использующие указатели или ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.”
Важность LSP
LSP обеспечивает гибкость и повторное использование кода. Соблюдение этого принципа позволяет создавать более устойчивые и поддерживаемые системы.
Примеры
Пример 1: Корректное использование LSP
Базовый класс
class Rectangle {public:    virtual void setWidth(double width) {        this->width = width;    }
    virtual void setHeight(double height) {        this->height = height;    }
    virtual double getWidth() const {        return width;    }
    virtual double getHeight() const {        return height;    }
    virtual double area() const {        return width * height;    }
protected:    double width;    double height;};Производный класс
class Square : public Rectangle {public:    void setWidth(double side) override {        this->width = side;        this->height = side;    }
    void setHeight(double side) override {        this->width = side;        this->height = side;    }};Пример использования
void process(Rectangle& rect) {    rect.setWidth(5);    rect.setHeight(10);    std::cout << "Area: " << rect.area() << std::endl;}
int main() {    Rectangle rect;    Square sq;
    process(rect); // Корректный вывод: Area: 50    process(sq);   // Корректный вывод: Area: 100
    return 0;}Пример 2: Нарушение LSP
Базовый класс
class Bird {public:    virtual void fly() {        std::cout << "Bird is flying" << std::endl;    }};Производный класс
class Penguin : public Bird {public:    void fly() override {        std::cout << "Penguins cannot fly" << std::endl;    }};Пример использования
void letBirdFly(Bird& bird) {    bird.fly();}
int main() {    Bird bird;    Penguin penguin;
    letBirdFly(bird);    // Корректный вывод: Bird is flying    letBirdFly(penguin); // Некорректный вывод: Penguins cannot fly
    return 0;}В данном примере, передача объекта Penguin в функцию letBirdFly нарушает принцип подстановки Лисков, так как Penguin не соответствует ожиданиям базового класса Bird.
Как соблюдать LSP
Контрактный дизайн
- Базовый и производный классы должны соблюдать общий контракт, что означает выполнение всех ожидаемых операций.
 
Избегайте нарушения предусловий и постусловий
- Предусловия не должны быть усилены в подклассе, а постусловия не должны быть ослаблены.
 
Инварианты
- Подклассы должны сохранять инварианты базовых классов.
 
Резюме
- Принцип подстановки Лисков гласит, что объекты подклассов должны быть взаимозаменяемы с объектами их базовых классов.
 - Соблюдение LSP улучшает гибкость, повторное использование кода и поддерживаемость системы.
 - Корректное использование LSP: подклассы сохраняют поведение базового класса.
 - Нарушение LSP: подклассы изменяют поведение базового класса, что может привести к неожиданным результатам.
 - Рекомендации: соблюдать контрактный дизайн, предусловия, постусловия и инварианты базовых классов.