Skip to main content

Examples and Non-Examples of Liskov Substitution Principle

Liskov Substitution Principle (LSP)

Examples and Non-Examples

Good Example Here is a code snippet where LSP is properly implemented:

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

class Sparrow extends Bird {
// Sparrow inherits fly method and doesn't alter the behavior
}

public class Main {
public static void main(String[] args) {
Bird bird = new Sparrow();
bird.fly(); // Outputs "Flying"
}
}

Violation Example Here is a code snippet that violates LSP and an explanation of why it is a violation:

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

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

public class Main {
public static void main(String[] args) {
Bird bird = new Ostrich();
bird.fly(); // Throws UnsupportedOperationException
}
}

The above examples violate LSP because the subclass Ostrich does not adhere to the expected behavior of the superclass Bird. An Ostrich cannot be substituted for a Bird without altering the program's correctness.

Refactored Example To adhere to LSP, we can refactor the code by introducing a new hierarchy:

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

class FlyingBird extends Bird {
@Override
public void move() {
fly();
}

public void fly() {
System.out.println("Flying");
}
}

class NonFlyingBird extends Bird {
@Override
public void move() {
walk();
}

public void walk() {
System.out.println("Walking");
}
}

class Sparrow extends FlyingBird {
// Sparrow inherits fly method and move behavior
}

class Ostrich extends NonFlyingBird {
// Ostrich inherits walk method and move behavior
}

public class Main {
public static void main(String[] args) {
Bird sparrow = new Sparrow();
Bird ostrich = new Ostrich();

sparrow.move(); // Outputs "Flying"
ostrich.move(); // Outputs "Walking"
}
}

Real-World Scenario Consider a payment processing system in an e-commerce platform. You might have a base class PaymentMethod with subclasses CreditCard, PayPal, and BankTransfer. Each payment method should implement a processPayment method that adheres to the contract defined by the PaymentMethod class.

By adhering to LSP, you can ensure that new payment methods can be added without altering the existing code. For instance, adding a new BitcoinPayment method should not require changes to the code that processes payments; it should seamlessly integrate with the existing system.

By understanding and applying these examples, developers can see the practical implications of LSP and how it ensures reliable and maintainable code.