So you know how to code in general, understand the object-oriented programming, learned C++, and completed at least one Software Development Course (if you're not there yet, these articles aren't for you). You can write software easily if you know at least one programming language, but is your code any good? Could it be done any better? Is it clean (and what on earth does that mean)? Is your architecture any good Should you use a different one? These were some of the questions I've had when I started, and answering them helped me to step up to a professional level. Which is why I have written these series SOLID as a Rock design principle. iskov's ubstitution rinciple in C++ is the second principle in this series which I will discuss here. What about Design Patterns? L S P The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing "public:" sometimes and also miss , constructor, , prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons. virtual destructor copy constructor Note: If you stumbled here directly, then I would suggest you go through first, even if it is trivial. I believe it will encourage you to explore more on this topic. What is design pattern? All of this code you encounter in this series of articles are compiled using C++20(though I have used features up to C++17 in most cases). So if you don't have access to the latest compiler you can use which has preinstalled boost library as well. Modern C++ https://wandbox.org/ Intent Subtypes must be substitutable for their base types without altering the correctness of the program If I address this in the context of C++, this literally means that functions that use pointers/references to base classes must be able to substitute by its derived classes. The Liskov Substitution Principle revolves around ensuring that inheritance is used correctly. Violating the Liskov’s Substitution Principle A great & traditional example illustrating LSP was how sometimes something that sounds right in natural language doesn't quite work in code. In mathematics, a Square is a Rectangle. Indeed it is a specialization of a rectangle. The "IS A" makes you want to model this with inheritance. However if in code you made Square derive from Rectangle, then a Square should be usable anywhere you expect a Rectangle. This makes for some strange behaviour as follows: Rectangle( width, height) : m_width{width}, m_height{height} {} get_width() { m_width; } get_height() { m_height; } { ->m_width = width; } { ->m_height = height; } area() { m_width * m_height; } : m_width, m_height; }; Rectangle { Square( size) : Rectangle(size, size) {} { ->m_width = m_height = width; } { ->m_height = m_width = height; } }; { w = r.get_width(); r.set_height( ); assert((w * ) == r.area()); } { Rectangle r{ , }; process(r); Square s{ }; process(s); EXIT_SUCCESS; } { struct Rectangle const uint32_t const uint32_t uint32_t const return uint32_t const return virtual void set_width ( width) const uint32_t this virtual void set_height ( height) const uint32_t this uint32_t const return protected uint32_t : struct Square uint32_t override void set_width ( width) const uint32_t this override void set_height ( height) const uint32_t this void process (Rectangle &r) uint32_t 10 10 // Fails for Square <-------------------- int main () 5 5 5 return As you can see above, we have violated Liskovs's Substitution Principle in the void process(Rectangle &r) function. Therefore Square is not a valid substitute of Rectangle. If you see from the design perspective, the very idea of inheriting Square from Rectangle is not a good idea. Because Square does not have height & width, rather it has the size/length of sides. Example of Liskov’s Substitution Principle in C++ Not so good { w = r.get_width(); r.set_height( ); ( <Square *>(&r) != ) assert((r.get_width() * r.get_width()) == r.area()); assert((w * ) == r.area()); } void process (Rectangle &r) uint32_t 10 if dynamic_cast nullptr else 10 A common code smell that frequently indicates an LSP violation is the presence of code within a code block that is polymorphic. type checking For instance, if you have a std::for_each loop over a collection of objects of type Foo, and within this loop, there is a check to see if Foo is in fact Bar(a subtype of Foo), then this is almost certainly an LSP violation. Rather you should ensure Bar is in all ways substitutable for Foo, there should be no need to include such a check. An OK way to do it { w = r.get_width(); r.set_height( ); (r.is_square()) assert((r.get_width() * r.get_width()) == r.area()); assert((w * ) == r.area()); } void process (Rectangle &r) uint32_t 10 if else 10 No need to create a separate class for Square. Instead, you can simply check for bool flag within the Rectangle class to validate Square property. Though not a recommended way. Use proper inheritance hierarchy = ; }; Shape { Rectangle( width, height) : m_width{width}, m_height{height} {} get_width() { m_width; } get_height() { m_height; } { ->m_width = width; } { ->m_height = height; } area() override { m_width * m_height; } : m_width, m_height; }; Shape { Square( size) : m_size(size) {} { ->m_size = size; } area() override { m_size * m_size; } : m_size; }; { } { struct Shape uint32_t virtual area () const 0 : struct Rectangle const uint32_t const uint32_t uint32_t const return uint32_t const return virtual void set_width ( width) const uint32_t this virtual void set_height ( height) const uint32_t this uint32_t const return private uint32_t : struct Square uint32_t void set_size ( size) const uint32_t this uint32_t const return private uint32_t void process (Shape &s) // Use polymorphic behaviour only i.e. area() With Factory Pattern Still, creation or change is needed to process Shape, then you should try to use & i.e. . Virtual Constructor Virtual Copy Constructor Factory Pattern ; ; }; { struct ShapeFactory Shape static CreateRectangle ( width, height) uint32_t uint32_t Shape static CreateSquare ( size) uint32_t Benefits of Liskov’s Substitution Principle => Compatibility It enables the binary compatibility between multiple releases & patches. In other words, It keeps the client code away from being impacted. => Type Safety It’s the easiest approach to handle type safety with inheritance, as types are not allowed to when inheriting. vary => Maintainability Code that adheres to LSP is loosely dependent on each other & encourages code reusability. Code that adheres to the LSP is code that makes the right abstractions. Yardstick to Craft Liskov’s Substitution Principle Friendly Software in C++ In most introductions to , inheritance discussed as an “IS-A” relationship with the inherited object. However, this is necessary, but not sufficient. It is more appropriate to say that one object can be designed to inherit from another if it always has an “IS-SUBSTITUTABLE-FOR” relationship with the inherited object. object-oriented programming The whole point of using an abstract base class is so that, in the future, you can write a new & insert it into existing, working, tested code. A noble goal, but how to achieve it? First, start with decomposing your problem space — domain. Second, express your contract/interfaces/ in plain English. subclass virtual-methods Closing Notes Don’t get me wrong, I like SOLID and the approaches it promotes. But it’s just a shape of deeper principles lying in its foundation. The examples above made it clear what this principle is striving for i.e. . loose coupling & ensuring correct inheritance Now, go out there and make your subclasses swappable, and thank for such a useful principle. Dr. Barbara Liskov /!\: Originally published @ www.vishalchovatiya.com . If you haven't gone through my previous articles on design principles, then below is the quick links: SRP -- Single Responsibility Principle OCP -- Open/Closed Principle LSP -- Liskov Substitution Principle ISP -- Interface Segregation Principle DIP -- Dependency Inversion Principle