SOLID - Liskov Substitution Principle (LSP)

SOLID - Liskov Substitution Principle (LSP)

The Liskov Substitution Principle (LSP) is a fundamental principle in object-oriented programming (OOP) that defines guidelines for substitutability of objects within a program's inheritance hierarchy. It states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.

In simpler terms, the LSP emphasizes that derived classes or subclasses must be able to be used in place of the base class without introducing unexpected behavior or breaking the functionality of the program.

The principle can be summarized with the following statement: "If S is a subtype of T, then objects of type T can be replaced with objects of type S without altering the correctness of the program."

Design by Contract (DbC)

To adhere to the Liskov Substitution Principle, derived classes must follow these guidelines:

Subtype Requirement: The contract or behavior defined by the base class must be preserved in the derived class. This means that the derived class should not introduce new exceptions, weaken preconditions, or strengthen postconditions compared to the base class.

Consistent Return Types: The methods in the derived class should have the same return types or covariant return types as the methods in the base class. This ensures that the calling code can rely on consistent behavior and return types.

No Violation of Invariants: The invariants (behavioral properties) of the base class must be maintained in the derived class. Any assumptions made by the client code using the base class should hold true for the derived class as well.

Violation of LSP

In this example, we have a Rectangle class with a width and height attribute, and methods to set the width and height, as well as calculate the area. We also have a Square class that inherits from Rectangle. However, the Square class violates the Liskov Substitution Principle.

Although a square is a special case of a rectangle, it has a constraint that the width and height must always be equal. In the Square class, when setting the width or height, both attributes are updated to the same value. This violates the behavior expected from a rectangle because changing one side should not affect the other side.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def set_width(self, width):
        self.width = width
    
    def set_height(self, height):
        self.height = height
    
    def area(self):
        return self.width * self.height

class Square(Rectangle):
    def set_width(self, width):
        self.width = width
        self.height = width
    
    def set_height(self, height):
        self.width = height
        self.height = height        

Correct Use - Adhering to LSP

In this corrected example, we introduce an abstract Shape class that defines a common area() method. Both the Rectangle and Square classes inherit from Shape, and they implement the area() method according to their specific behavior. The Square class no longer inherits from Rectangle, avoiding the violation of the Liskov Substitution Principle. Each class maintains its own behavior without conflicting or introducing unexpected behavior.

By adhering to the Liskov Substitution Principle, we ensure that the derived classes (subclasses) can be substituted for their base class (superclass) without breaking the expected behavior of the program.

class Shape:
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def set_width(self, width):
        self.width = width
    
    def set_height(self, height):
        self.height = height
    
    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side_length):
        self.side_length = side_length
    
    def set_side_length(self, side_length):
        self.side_length = side_length
    
    def area(self):
        return self.side_length ** 2

Demonstration

In the print_area function below, it takes a Shape object as an argument. Both the Rectangle and Square objects are subclasses of Shape, and they correctly override the get_area method according to their specific implementations. The LSP ensures that the substitution is valid, meaning that the program behaves as expected when objects of the subclasses are used in place of the superclass object.

def print_area(shape):
    area = shape.get_area()
    print(f"The area is: {area}")

rectangle = Rectangle(4, 5)
print_area(rectangle)

square = Square(4)
print_area(square)

The print_area function, we define it to accept a Shape object. This function can work with any subclass of Shape that adheres to the contract of the Shape class (implementing the get_area method).

Open-Closed Principle (OCP)

In the example, the Shape superclass is open for extension because new subclasses, such as Rectangle and Square, can be added without modifying the Shape class. This demonstrates the spirit of the Open-Closed Principle.

Popular posts from this blog

Atom - Jupyter / Hydrogen

Design Patterns

Robson Koji Moriya disambiguation name