Dezign Patterns
Liskov Substitution Principle (LSP)
📜 Definition : Subtypes must be replaceable for their base types without breaking functionality.🎯 Intent : If class B is a subclass of A, you should be able to use B wherever you use A without errors or unexpected behavior.
✅ Good example:
class Bird {
void fly() {}
}
class Sparrow extends Bird {} // OK ✅
❌ liskov Substitution Principle Violation Example
🧠 General Rule of Thumb:
If your subclass implemneted in such a way where it breaks the parent class/interface contract, it's probably violating LSP. 👀
Note : Follow ISP to avoid bloated interfaces, but don't over-segment interfaces to the point where code becomes fragmented and unmaintainable.
class Ostrich extends Bird {
void fly() {
throw new UnsupportedOperationException(); // Ostrich can't fly!
}
}
In Simple words base class is a Specification which says how it needs to be implemented, If a subclass is implemented by applying some other logic, then we will not be able to replace parent , which leads to break of LSP
Liskov Substitution Principle can be violated in various ways, including
- incompatible method signatures
- weakening preconditions
- changing inherited behavior
- introducing state inconsistencies.
class BankAccount {
public void withdraw(double amount) {
// Withdrawal logic
}
}
class CheckingAccount extends BankAccount {
@Override
public void withdraw(double amount) {
if (amount > 1000) {
throw new IllegalArgumentException("Cannot withdraw more than $1000 from Checking Account.");
}
super.withdraw(amount);
}
}
class PaymentProcessor {
public void processPayment() { System.out.println("Payment Processed"); }
}
class RefundProcessor extends PaymentProcessor {
@Override
public void processPayment() {
// Refund logic that may fail
throw new UnsupportedOperationException("Refunds not supported here");
}
}
Account {
deposit()
withdraw()
}
Saving implements Account {
}
Current implements Account {
}
Fixed implements Account {
withdraw() {
throws Unsupported Op Expression // braks LSP , solve it using interface seggrigation
}
}
class Rectangle {
protected int width;
protected int height;
public void setWidth(int w) {
this.width = w;
}
public void setHeight(int h) {
this.height = h;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int w) {
this.width = w;
this.height = w; // Forces height = width
}
@Override
public void setHeight(int h) {
this.width = h;
this.height = h; // Forces width = height
}
}
public class TestLSP {
public static void main(String[] args) {
Rectangle rect = new Square();
rect.setWidth(5);
rect.setHeight(10);
System.out.println("Expected area = 5 * 10 = 50");
System.out.println("Actual area = " + rect.getArea());
}
}
Instead of making Square extend Rectangle, prefer composition over inheritance, or use separate classes/interfaces if they have different behavior contracts.
interface Shape {
int getArea();
}
class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
// Deposit method guarantees that the balance will be greater than or equal to the original balance
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public double getBalance() {
return balance;
}
}
class PremiumAccount extends BankAccount {
public PremiumAccount(double initialBalance) {
super(initialBalance);
}
// Deposit method guarantees that the balance will be strictly greater than the original balance
@Override
public void deposit(double amount) {
super.deposit(amount); // Call the superclass method
if (amount > 500) {
// Apply a special bonus for large deposits in PremiumAccount
double bonus = amount * 0.05;
super.deposit(bonus); // Add bonus to balance
}
}
}