Перейти к содержимому

Подстановочный принцип Барборы Лисков

Введение

Принцип подстановки Барбары Лисков (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: подклассы изменяют поведение базового класса, что может привести к неожиданным результатам.
  • Рекомендации: соблюдать контрактный дизайн, предусловия, постусловия и инварианты базовых классов.