What is an example of the Liskov Substitution Principle?
Example of the Liskov Substitution Principle
The Liskov Substitution Principle (LSP) is one of the foundational principles in object-oriented programming and part of the SOLID principles. LSP 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 class S is a subtype of class T, then objects of type T should be replaceable with objects of type S without altering the desirable properties of the program (e.g., correctness, task performed).
Understanding Liskov Substitution Principle
Key Points:
- Substitutability: Subclasses should be substitutable for their base classes.
- Behavior Preservation: The behavior of the program should remain consistent when a subclass replaces a base class.
- No Strengthened Preconditions: Subclasses should not impose stricter conditions than their base classes.
- No Weakened Postconditions: Subclasses should not promise less than their base classes.
Practical Example in Python
Let's consider a classic example involving geometric shapes: Rectangle and Square.
Base Class: Rectangle
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 get_area(self): return self.width * self.height
Subclass: Square
class Square(Rectangle): def set_width(self, width): self.width = width self.height = width # Ensuring square property def set_height(self, height): self.height = height self.width = height # Ensuring square property
Function Utilizing Rectangle
def process_rectangle(rect): rect.set_width(5) rect.set_height(10) assert rect.get_area() == 50, "Area should be 50"
Using Rectangle
rect = Rectangle(2, 3) process_rectangle(rect) # Passes assertion print(f"Rectangle Area: {rect.get_area()}") # Output: Rectangle Area: 50
Using Square (Violation of LSP)
square = Square(2, 2) process_rectangle(square) # Assertion fails print(f"Square Area: {square.get_area()}") # Output: Square Area: 100
Explanation:
-
Expected Behavior:
- For a
Rectanglewith width5and height10, the area should be50.
- For a
-
Using
Rectangle:- The
process_rectanglefunction sets the width to5and height to10. - The area correctly computes to
50, passing the assertion.
- The
-
Using
Square:- The
process_rectanglefunction sets the width to5, which also sets the height to5due to the overridden methods inSquare. - Then, it sets the height to
10, which also sets the width to10. - The expected area was
50, but the actual area is100, causing the assertion to fail.
- The
Violation of LSP:
- The
Squareclass alters the expected behavior of theRectangleclass by enforcing equal width and height. This change leads to unexpected results when aSquareis used in place of aRectangle, violating the Liskov Substitution Principle.
Correcting the Violation
To adhere to LSP, ensure that subclasses do not alter the expected behavior of their base classes. One way to resolve this is by avoiding inheritance in cases where it doesn't make sense.
Using Composition Instead of Inheritance
Instead of having Square inherit from Rectangle, use composition to include a Rectangle within Square.
class Square: def __init__(self, side_length): self.rectangle = Rectangle(side_length, side_length) def set_side(self, side_length): self.rectangle.set_width(side_length) self.rectangle.set_height(side_length) def get_area(self): return self.rectangle.get_area()
Updated Function
def process_shape(shape): if isinstance(shape, Rectangle): shape.set_width(5) shape.set_height(10) assert shape.get_area() == 50, "Area should be 50"
Using Rectangle and Square
rect = Rectangle(2, 3) process_shape(rect) # Passes assertion print(f"Rectangle Area: {rect.get_area()}") # Output: Rectangle Area: 50 square = Square(2) process_shape(square.rectangle) # Passes assertion print(f"Square Area: {square.get_area()}") # Output: Square Area: 50
Explanation:
- By using composition,
Squaremanages its own behavior without altering theRectangleclass. - The
process_shapefunction remains compatible withRectangleinstances. - This approach maintains the substitutability without violating LSP.
Additional Example: Payment System
Base Class: Payment
class Payment: def pay(self, amount): raise NotImplementedError("Subclasses should implement this!")
Subclass: CreditCardPayment
class CreditCardPayment(Payment): def pay(self, amount): print(f"Processing credit card payment of ${amount}")
Subclass: PayPalPayment
class PayPalPayment(Payment): def pay(self, amount): print(f"Processing PayPal payment of ${amount}")
Function Utilizing Payment
def process_payment(payment_method, amount): payment_method.pay(amount) print("Payment processed successfully.")
Using Subclasses
credit_card = CreditCardPayment() paypal = PayPalPayment() process_payment(credit_card, 100) # Output: # Processing credit card payment of $100 # Payment processed successfully. process_payment(paypal, 200) # Output: # Processing PayPal payment of $200 # Payment processed successfully.
Explanation:
- Both
CreditCardPaymentandPayPalPaymentare subclasses ofPayment. - They implement the
paymethod as required. - The
process_paymentfunction can seamlessly work with any subclass ofPayment, adhering to LSP.
Summary
The Liskov Substitution Principle ensures that subclasses can stand in for their base classes without disrupting the behavior of the program. By adhering to LSP, you create more robust, maintainable, and flexible code. Avoid scenarios where subclasses alter the expected behavior of base classes, and consider using composition over inheritance when appropriate.
For a deeper understanding and more examples of the Liskov Substitution Principle and other object-oriented design principles, consider exploring the Grokking the Object Oriented Design Interview course on DesignGurus.io.
Happy Coding!
GET YOUR FREE
Coding Questions Catalog
$197

$78
$78