Skip to content

Liskov Substitution Principle

Subrata Sahoo edited this page Jul 20, 2024 · 4 revisions

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

What does it mean?

It states that objects of a superclass should be replaceable with objects of a subclass without affecting the functionality of a program. This principle ensures that a subclass can stand in for its superclass.

Advantages

  • Ensure Correctness and Reliability: LSP helps prevent bugs and errors that might arise from unexpected behaviour.
  • Promote Robustness: LSP promotes a more robust system where components can be substituted without causing the system to fail.
  • Facilitate Code Reusability: LSP encourages the design of classes in a way that maximizes reuse. Subclasses can extend the functionality of super classes without altering their expected behaviour, making it easier to reuse existing code.
  • Enhance Maintainability: Easier to maintain and extend the code. Changes in one part of the hierarchy do not introduce side effects in other parts.
  • Enable Polymorphism: LSP is fundamental to achieving true polymorphism in object-oriented programming. It ensures that polymorphic behaviour is predictable and reliable.

Pitfalls

Problem Description Solution
Violation of Behavioural Contracts A subclass may override methods from the superclass but not adhere to the expected behaviour or contract of those methods. This can lead to unexpected behaviour or bugs when a subclass is used in place of a superclass. Ensure that subclasses honour the behavioural contracts of the superclass. Subclasses should provide implementations that are consistent with the expectations set by the superclass.
Inconsistent State Handling A subclass might introduce state-related changes that do not align with the superclass's expectations or invariants. This can lead to incorrect behaviour when subclasses are used in place of the superclass. Maintain consistency in state handling between the superclass and subclass. Ensure that the subclass preserves the invariants and constraints defined by the superclass.
Overriding Methods with Different Exceptions A subclass might override a method and throw exceptions that are not thrown by the superclass method. This can cause issues if the superclass code relies on specific exception handling behaviour. Ensure that subclass methods do not throw exceptions that the superclass method does not throw, or clearly document any new exceptions.
Violation of Subtype Polymorphism Subclasses may alter the expected behaviour in a way that breaks the polymorphic use of the superclass. This can lead to issues when subclass objects are used in polymorphic contexts. Adhere to the principle of behavioural subtyping, ensuring that subclasses can be used interchangeably with the superclass without altering the expected behaviour.
Complex Inheritance Hierarchies Deep or complex inheritance hierarchies can make it challenging to ensure that all subclasses adhere to LSP. Inherited methods and properties might be inadvertently misused or misrepresented. Use composition over inheritance where appropriate. Keep inheritance hierarchies as simple and shallow as possible to minimize complexity.
Lack of Proper Testing If subclasses are not thoroughly tested, there may be unrecognized violations of LSP that only become apparent when the code is used in production. Implement comprehensive unit tests for both the superclass and its subclasses. Ensure that tests cover various use cases and scenarios to detect potential LSP violations.
Misunderstanding of Substitutability Developers might misunderstand what it means for a subclass to be a true substitute for its superclass, leading to incorrect implementations or expectations. Educate developers about the principles of substitutability and ensure a clear understanding of the superclass's contract and behaviour.
Unintended Side Effects Changes in a subclass may unintentionally affect other parts of the system that depend on the superclass. This can lead to unforeseen side effects and bugs. Carefully evaluate changes in subclasses and their potential impact on the overall system. Implement robust integration testing to catch unintended side effects.

Example Explained

Case 1

In this example, Penguin violates the Liskov Substitution Principle because it changes the expected behaviour of the Fly method. To adhere more closely to LSP, we need to redesign the class hierarchy.

In the improved example:

  • Bird is an abstract class with an abstract Move method.
  • FlyingBird and NonFlyingBird are subclasses of Bird that implement the Move method appropriately.
  • Sparrow inherits from FlyingBird and Penguin inherits from NonFlyingBird.

This way, substituting any FlyingBird or NonFlyingBird for a Bird will not violate the expectations about their behavior, adhering to the Liskov Substitution Principle.

Case 2

In this example, BeverageItem violates the Liskov Substitution Principle because it contains an additional method which cannot be called via the base class. To adhere more closely to LSP, we need to redesign the classes. In the improved example, MenuItem also has the GetDiscount Method. This way, substituting any MenutItem for a BeverageItem to call GetDiscount will not violate the Liskov Substitution Principle.

Clone this wiki locally