In essence, the Liskov Substitution Principle (LSP) states that if a function works with a pointer or reference to a base object, it must also work with a pointer or reference to any of its derived objects. This rule is sometimes broken because the techniques we apply in source code do not always work in real-world abstractions.
A famous example is a square and a rectangle. Mathematically speaking, the former is a specialization of the latter, so there's an "is a" relationship from one to the other. This tempts us to create a Square class that inherits from the Rectangle class. So, we could end up with code like the following:
class Rectangle {
public:
virtual ~Rectangle() = default;
virtual double area() { return width_ * height_; }
virtual void setWidth(double width) { width_ = width; }
virtual void setHeight(double height) { height_ = height; }
private:
double width_;
double height_;
};
class Square : public Rectangle {
public:
double area() override;
void setWidth(double width) override;
void setHeight(double height) override;
};
How should we implement the members of the Square class? If we want to follow the LSP and save the users of such classes from surprises, we can't: our square would stop being a square if we called setWidth. We can either stop having a square (not expressible using the preceding code) or modify the height as well, thus making the square look different than a rectangle.
If your code violates the LSP, it's likely that you're using an incorrect abstraction. In our case, Square shouldn't inherit from Rectangle after all. A better approach could be making the two implement a GeometricFigure interface.
Since we are on the topic of interfaces, let's move on to the next item, which is also related to them.