Design Principles: Liskov Substitution Principle

Design Principles: Liskov Substitution Principle

Tags
Software Development
Design
Published
December 19, 2024
Author
Mohit Srivastava
The Liskov Substitution Principle (LSP) is a key principle of object-oriented programming. It ensures that subclasses can replace their parent classes without breaking the functionality of the system. Let’s explore this with a relatable example involving Vehicle, MotorVehicle, and Bicycle.

Vehicle Example

We start with a base class Vehicle representing general vehicles. Subclasses like MotorVehicle (e.g., cars) and Bicycle represent specific types of vehicles.
Initial Design (Violates LSP)
class Vehicle: def start_engine(self): raise NotImplementedError("This method should be implemented by subclasses") class MotorVehicle(Vehicle): def start_engine(self): print("Engine started") class Bicycle(Vehicle): def start_engine(self): raise Exception("Bicycles do not have an engine")
In this design: • The start_engine method in Vehicle implies that all vehicles have an engine. • However, bicycles do not have engines. The Bicycle class violates the LSP because substituting a Vehicle with a Bicycle in the code could cause runtime errors when calling start_engine.
 
Improved Design (Adhering to LSP)
We introduced the 2 more classes Motorized vehicle and Manual Vehicle. This avoids forcing behaviors like starting an engine on vehicles that don’t have one.
class Vehicle: def __init__(self, name: str, speed: float): self.name = name self.speed = speed def display_info(self): print(f"Vehicle Name: {self.name}, Speed: {self.speed} km/h") class Motorized(Vehicle): def start_engine(self): raise NotImplementedError("Motorized vehicles must implement the start_engine method") class Manual(Vehicle): def start_moving(self): raise NotImplementedError("Manual vehicles must implement the start_moving method") class Car(Motorized): def start_engine(self): print(f"{self.name}: Engine started and ready to move!") class Bicycle(Manual): def start_moving(self): print(f"{self.name}: Pedaling to start moving!")
 
classDiagram class Vehicle { +name: string +speed: double +display_info(): void } class Motorized { +startEngine(): void } class Manual { +startMoving(): void } class Car { +startEngine(): void } class Bicycle { +startMoving(): void } Vehicle <|-- Motorized Vehicle <|-- Manual Motorized <|-- Car Manual <|-- Bicycle
 
With this implementation, we have satisfied the LSP.
  • Car is substitutable with its superclass, Motorized, and Bicycle is substitutable with its superclass, Manual, without breaking the functionality.
  • Their methods can also override the methods of the superclass.

Conclusion

The LSP is an important principle that should be extended to the level of system architecture. A small violation of the substitutability of classes can cause the system to break down, which is why we should always be on the lookout for violations. A few benefits of the LSP are provided below:
  • It avoids the generalization of concepts that may not be needed in the future.
  • It makes the code maintainable and easier to upgrade.