The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In simpler terms, if a class B
is a subclass of class A
, then we should be able to use B
wherever we use A
without any issues.
Example of LSP Violation
Consider the following classes:
class Bird:
def fly(self):
return "Flying"
class Sparrow(Bird):
pass
class Ostrich(Bird): # Ostrich cannot fly
def fly(self):
raise Exception("Ostriches can't fly!")
In this example, Sparrow
is a true subtype of Bird
because it can fly. However, Ostrich
violates the LSP because it cannot fly, even though it is a subclass of Bird
. If we replace a Bird
object with an Ostrich
object in a program expecting all birds to fly, it will lead to an error.
Correcting the Violation
To adhere to LSP, we can refactor the classes:
class Bird:
def make_sound(self):
return "Some sound"
class FlyingBird(Bird):
def fly(self):
return "Flying"
class Sparrow(FlyingBird):
def make_sound(self):
return "Chirp"
class Ostrich(Bird):
def make_sound(self):
return "Boom"
Now, Sparrow
and Ostrich
are both valid subtypes of Bird
, and we can use them interchangeably without violating the LSP.
2. Avoiding “Inheritance for Code Reuse Only”
Inheritance should represent a logical “is-a” relationship rather than being used solely for code reuse. If a subclass does not logically represent a type of the superclass, it can lead to confusion and maintenance issues.
Example of Misuse of Inheritance
class Vehicle:
def start_engine(self):
return "Engine started"
class Car(Vehicle):
def start_engine(self):
return "Car engine started"
class Bicycle: # Not a Vehicle
def start_engine(self):
return "Bicycle doesn't have an engine"
In this example, Bicycle
is not a type of Vehicle
, yet it is trying to implement a method that belongs to Vehicle
. This creates confusion because a bicycle does not logically fit into the vehicle hierarchy.
Correcting the Misuse
Instead of using inheritance, we can use composition or interfaces to represent the relationship more accurately:
class Vehicle:
def start_engine(self):
return "Engine started"
class Car(Vehicle):
def start_engine(self):
return "Car engine started"
class Bicycle:
def ride(self):
return "Riding the bicycle"
In this corrected version, Bicycle
is no longer a subclass of Vehicle
, which clarifies the relationship and avoids confusion. Each class has its own responsibilities and behaviors without misrepresenting their relationships.
Conclusion
In summary, when designing classes with single inheritance:
- Ensure that subclasses adhere to the Liskov Substitution Principle, meaning they can be used interchangeably with their parent class without causing issues.
- Avoid using inheritance merely for code reuse; instead, focus on logical “is-a” relationships to maintain clarity and correctness in your class hierarchy.