Liskov Substitution Principle in Java with Example

Introduction

The Liskov Substitution Principle (LSP) is one of the five SOLID principles of object-oriented design. It states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. This principle ensures that a subclass can stand in for its superclass without altering the desirable properties of the program (correctness, task performed, etc.).

Table of Contents

  1. What is the Liskov Substitution Principle?
  2. Benefits of the Liskov Substitution Principle
  3. Example: Violation of LSP
  4. Example: Adherence to LSP
  5. Real-World Example
  6. Conclusion

1. What is the Liskov Substitution Principle?

The Liskov Substitution Principle (LSP) asserts that if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of the program. This means that subclasses should extend the functionality of the parent class without changing its behavior.

2. Benefits of the Liskov Substitution Principle

  • Enhanced Reusability: Ensures subclasses can be used interchangeably with their parent classes.
  • Improved Maintainability: Reduces the risk of introducing bugs when extending classes.
  • Increased Flexibility: Allows for more flexible and modular code design.

3. Example: Violation of LSP

In this example, we'll create a superclass Bird and a subclass Penguin that violates LSP.

Example:

class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
}

class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly");
    }
}

public class Main {
    public static void main(String[] args) {
        Bird bird = new Sparrow();
        bird.fly(); // Output: Sparrow is flying

        bird = new Penguin();
        bird.fly(); // Throws UnsupportedOperationException
    }
}

Issues:

  • The Penguin class violates LSP because it changes the behavior of the fly method by throwing an exception.
  • This breaks the expected behavior of the Bird class and can lead to runtime errors.

4. Example: Adherence to LSP

To adhere to LSP, we can introduce a more appropriate class hierarchy where flying ability is modeled differently.

Example:

abstract class Bird {
    public abstract void eat();
}

class Sparrow extends Bird {
    @Override
    public void eat() {
        System.out.println("Sparrow is eating");
    }

    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

class Penguin extends Bird {
    @Override
    public void eat() {
        System.out.println("Penguin is eating");
    }

    public void swim() {
        System.out.println("Penguin is swimming");
    }
}

public class Main {
    public static void main(String[] args) {
        Bird sparrow = new Sparrow();
        sparrow.eat(); // Output: Sparrow is eating
        ((Sparrow) sparrow).fly(); // Output: Sparrow is flying

        Bird penguin = new Penguin();
        penguin.eat(); // Output: Penguin is eating
        ((Penguin) penguin).swim(); // Output: Penguin is swimming
    }
}

Explanation:

  • Bird: An abstract class that provides a common interface for all bird types.
  • Sparrow and Penguin: Subclasses that extend Bird without changing its behavior.
  • Main: Demonstrates the use of LSP by allowing subclasses to be used interchangeably with the parent class.

5. Real-World Example

Example: Payment Processing System

Consider a payment processing system where different payment methods (e.g., credit card, PayPal) should adhere to the Liskov Substitution Principle.

Step 1: Define the Payment Class

abstract class Payment {
    public abstract void processPayment(double amount);
}

class CreditCardPayment extends Payment {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

class PayPalPayment extends Payment {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}

Step 2: Main Class to Demonstrate LSP

public class Main {
    public static void main(String[] args) {
        Payment payment = new CreditCardPayment();
        payment.processPayment(100.0); // Output: Processing credit card payment of $100.0

        payment = new PayPalPayment();
        payment.processPayment(200.0); // Output: Processing PayPal payment of $200.0
    }
}

Explanation:

  • Payment: An abstract class that provides a common interface for all payment methods.
  • CreditCardPayment and PayPalPayment: Subclasses that extend Payment without changing its behavior.
  • Main: Demonstrates the use of LSP by allowing different payment methods to be used interchangeably.

6. Conclusion

The Liskov Substitution Principle (LSP) is a fundamental concept in object-oriented design that ensures subclasses can replace their parent classes without altering the correctness of the program. By adhering to LSP, developers can create more flexible, reusable, and maintainable code. Understanding and applying LSP is essential for building robust and scalable Java applications.

Happy coding!

Comments