Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Creating flexible and extensible code with the Liskov Substitution Principle

April 7, 2023

Creating Flexible and Extensible Code with the Liskov Substitution Principle

In object-oriented programming, the Liskov Substitution Principle (LSP) is a fundamental principle that ensures the proper usage of subtypes in a way that does not break the behavior of the parent type. This principle, introduced by Barbara Liskov in 1987, is crucial for creating flexible and extensible code that can be easily maintained and scaled. In this blog post, we’ll explore the Liskov Substitution Principle with extensive examples in C and Python and explain the concepts in detail.

Understanding the Liskov Substitution Principle

The LSP states that objects of a superclass should be replaceable with objects of its subclass without affecting the correctness of the program. In other words, if S is a subtype of T, then objects of type T should be replaceable with objects of type S without altering the desirable properties of the program.

To ensure adherence to the LSP, it’s important to consider the following guidelines:

  1. The behavior of a subtype must be consistent with the behavior of its supertype.
  2. Preconditions cannot be strengthened in a subtype.
  3. Postconditions cannot be weakened in a subtype.
  4. Invariants of the supertype must be preserved in a subtype.

Example in C

Let’s consider a simple example in C to understand the Liskov Substitution Principle. We have a Shape superclass with a draw method, and two subclasses Circle and Square. The draw method in each subclass should behave consistently with the Shape superclass.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

typedef struct {
    void (*draw)(void);
} Shape;

void drawShape(Shape *shape) {
    shape->draw();
}

void drawCircle(void) {
    printf("Drawing a circle\n");
}

void drawSquare(void) {
    printf("Drawing a square\n");
}

int main() {
    Shape circle = {drawCircle};
    Shape square = {drawSquare};

    drawShape(&circle);
    drawShape(&square);

    return 0;
}

In this example, both Circle and Square subclasses implement the draw method in a way that is consistent with the behavior of the Shape superclass. This adherence to the LSP ensures that objects of type Shape can be seamlessly replaced with objects of its subclasses.

Example in Python

Now, let’s demonstrate the Liskov Substitution Principle in Python using a similar Shape example. In Python, we can leverage the power of inheritance to create flexible and extensible code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Shape:
    def draw(self):
        pass

class Circle(Shape):
    def draw(self):
        print("Drawing a circle")

class Square(Shape):
    def draw(self):
        print("Drawing a square")

def draw_shape(shape):
    shape.draw()

circle = Circle()
square = Square()

draw_shape(circle)
draw_shape(square)

In this Python example, the Circle and Square subclasses override the draw method from the Shape superclass, ensuring that the behavior of the subclasses is consistent with the behavior expected from the superclass. This adherence to the LSP allows for seamless interchangeability of objects of different types without disrupting the program’s functionality.

Conclusion

The Liskov Substitution Principle is a crucial aspect of object-oriented design that promotes code reusability, flexibility, and extensibility. By adhering to this principle, developers can create hierarchies of classes that are easy to maintain, scale, and modify without introducing unexpected behavior. In this blog post, we’ve explored the Liskov Substitution Principle with extensive examples in C and Python, demonstrating the importance of designing code with the LSP in mind for creating robust and adaptable software systems.


➡️ Get directory of currently run python script


⬅️ Virtual machine library


Go back to Posts.