I will put that simple question in the room: "Is a square a rectangle?" And you might thank: "Well dah, of course!"
But wait for a second and let's check it together. We will use the L in SOLID: The Liskov Substitution principle to check if this relationship makes sense for us!
Flashback to SOLID
I did write a whole article about the SOLID principles: "SOLID Principles in C#" so I will only go into the part why you are here: The Liskov Substitution Principle (LSP) can be summarized as follows: if a class is a subclass of another class, it should be able to replace its superclass (or interface) in the code without causing any issues. Essentially, we want the objects of our subclasses to behave in the same way as the objects of our superclass (or interface).
So now we can ask:
Is a square a rectangle?
So, is a square a rectangle? The answer is yes and no. Yes, because a square is a type of rectangle, and it inherits all the properties of a rectangle. However, no, because a square has additional constraints that do not apply to rectangles. A square has four equal sides, while a rectangle can have any combination of sides. Have a look at the following code:
public class Rectangle
{
public int Width { get; private set; }
public int Height { get; private set; }
public int Area => Width * Height;
public virtual void SetWidth(int width) => Width = width;
public virtual void SetHeight(int height) => Height = height;
}
public class Square : Rectangle
{
public override void SetWidth(int width) => (Width, Height) = (width, width);
public override void SetHeight(int heigth) => (Width, Height) = (heigth, heigth);
}
It seems straight-forward on first glance. Now let's take a look at the code provided. The code violates the Liskov Substitution Principle because the Square class modifies the behavior of the SetWidth
and SetHeight
methods from the Rectangle class. In the Rectangle class, SetWidth
and SetHeight
methods can set the width and height of the rectangle independently. But in the Square class, both methods set both the width and height to the same value.
This means that if we create an instance of the Square class and use it in place of a Rectangle instance, we might get unexpected results. For example, in the given test case, we set the width to 10 and height to 5. However, because we're using a Square instance, both width and height are set to 10 and afterward to 5, resulting in an area of 100 then 25 instead of 50 for a "regular" rectangle.
public class Tests
{
[Fact]
public void GivenSquareRectangle_CalculateArea()
{
Rectangle rectangle = new Square();
rectangle.SetWidth(10);
rectangle.SetHeigth(5);
// This will fail - but if we would adhere to
// The Liskov substitution principle that shouldn't happen
rectangle.Area.Should.Be(50);
}
}
Conclusion
All in all the Liskov Substitution Principle is a crucial principle in object-oriented programming, and violating it can lead to unexpected behavior in our programs. We must be aware of this principle and ensure that our code follows it to make our programs more maintainable and predictable.