Fixing the Liskov Substitution Principle Violation in C++ with Shape Hierarchy
To fix the violation of the Liskov Substitution Principle in the example with the Shape hierarchy in C++, we need to ensure that any subclass of Rectangle behaves in the same way as Rectangle in the context of the printArea() function.
One solution is to create a new base class that defines the interface for Rectangle and its subclasses:
class RectangleBase {
public:
virtual int getWidth() = 0;
virtual int getHeight() = 0;
};
class Shape : public RectangleBase {
public:
virtual int area() = 0;
int getWidth() override { return 0; }
int getHeight() override { return 0; }
};
class Rectangle : public Shape {
public:
int width;
int height;
int area() override {
return width * height;
}
int getWidth() override { return width; }
int getHeight() override { return height; }
};
class Square : public Shape {
public:
int side;
int area() override {
return side * side;
}
int getWidth() override { return side; }
int getHeight() override { return side; }
};
class SquareRectangle : public Rectangle {
public:
int area() override {
return getWidth() * getWidth();
}

You can also try this code with Online C++ Compiler
Run Code
In this solution, we create a new base class RectangleBase that defines the interface for Rectangle and its subclasses. The Shape class now inherits from RectangleBase and implements the getWidth() and getHeight() functions with default implementations that return 0.
The Rectangle and Square classes inherit from Shape and override the getWidth() and getHeight() functions to return their respective dimensions.
The SquareRectangle class, which caused the Liskov Substitution Principle violation, now inherits from Rectangle and can no longer override the area() function. Instead, it uses the getWidth() function to calculate the area of the shape.
With this solution, we have fixed the violation of the Liskov Substitution Principle by ensuring that any subclass of Rectangle behaves in the same way as Rectangle in the context of the printArea() function.
Tips for Applying Liskov Substitution Principle
-
Use inheritance to model "is-a" relationships: In C++, you can use inheritance to model relationships between classes. If a subclass "is-a" type of its superclass, then it can be used interchangeably with the superclass without changing the behavior of the code.
For example, let's say you have a Shape class and two subclasses, Rectangle and Square. Since a Square "is-a" Rectangle, you can use a Square anywhere you would use a Rectangle, without changing the behavior of the code.
class Shape {
public:
virtual int area() = 0;
};
class Rectangle : public Shape {
public:
int width;
int height;
int area() override {
return width * height;
}
};
class Square : public Rectangle {
public:
int side;
int area() override {
return side * side;
}
};

You can also try this code with Online C++ Compiler
Run Code
2. Use virtual functions to provide a consistent interface: In C++, you can use virtual functions to define a consistent interface for a group of related classes. Each subclass can provide its own implementation of the virtual function, while still maintaining the same behavior as the superclass.
For example, in the Shape class above, we defined a virtual function area(). Both Rectangle and Square provide their own implementation of area(), but they still follow the same behavior as the Shape class.
3. Avoid breaking the contract of the superclass: When writing a subclass, you should make sure that it doesn't break any rules or assumptions made by the superclass. For example, if the superclass requires a function to return a positive number, then the subclass should also return a positive number.
For example, if we add a new Circle class, we should make sure that its area() function always returns a positive number, just like the other classes.
class Circle : public Shape {
public:
int radius;
int area() override {
return 3.14 * radius * radius;
}
};

You can also try this code with Online C++ Compiler
Run Code
By following these tips, we can use the Liskov Substitution Principle to create a group of related classes that can be used interchangeably, without causing any unexpected errors or bugs.
Advantages of Using Liskov Substitution Principle
Following are the advantages of using the Liskov Substitution Principle (LSP) in object-oriented programming:
- Increases code reusability: By following LSP, we can create a set of related classes that can be used interchangeably without modifying the code. This makes it easier to reuse code and can save us time when we're developing new features.
- Simplifies code maintenance: When we follow LSP, we can make changes to one class without affecting the behavior of other classes in the group. This is because the subclass will still behave exactly like the superclass, and any changes made to the superclass will not affect its behavior.
- Promotes modularity: When we use LSP to create a set of related classes, each class can be treated as a standalone module, which means it can be developed and tested independently of other classes. It promotes modularity, as it allows us to break down a complex system into smaller and more manageable parts.
- Enhances code quality: When we follow LSP, we can be sure that every class in the group behaves consistently. This means that each class will implement the same set of methods and have the same behavior as the parent class. This consistency can help reduce the chances of catching bugs and unexpected behavior, thereby improving the overall quality of our code.
Frequently Asked Questions
What does the Liskov Substitution Principle ensure?
The Liskov Substitution Principle ensures that a class hierarchy is well-designed and behaves in a predictable manner.
What is the purpose of the Liskov Substitution Principle?
The purpose of the Liskov Substitution Principle is to make it easy to add new types to a system without having to change existing code.
How can the Liskov Substitution Principle be applied in practice?
The Liskov Substitution Principle can be applied by designing class hierarchies that follow the "is-a" relationship, ensuring that derived classes can be used in place of their base classes, and testing the behavior of derived classes to ensure that they behave correctly in all situations.
Conclusion
In conclusion, the Liskov Substitution Principle (LSP) is an important principle in object-oriented programming that helps promote code reusability, simplifies code maintenance, promotes modularity, and enhances code quality.
In this article, we discussed an example of how LSP can be violated in a Shape hierarchy in C++, and how this violation can lead to unexpected behavior and bugs. We also provided a solution to fix the violation and ensure that the Shape hierarchy conforms to LSP. Additionally, we provided some tips for applying LSP in your own code and answered some frequently asked questions.
By following LSP and creating a set of related classes that work together, we can create more maintainable, reusable and modular code. Implementing LSP can help us improve the overall quality of our code and can be easier to maintain and improve over time.
We have a step-by-step guided path that will teach you all the important concepts you need to know for system design. You can also consider our System Design Course to give your career an edge over others.
We want to hear from you. What do you think of this article?
If you have any questions or if there is any aspect related to LSP that you feel we missed, please let us know by leaving a comment below.