SOLID is a set of five design principles to make software maintainable, scalable, flexible, and testable.
S — Single Responsibility Principle (SRP)
A class should have only one reason to change.
❌ Before (Violation)
public class BankAccountService {
public void deposit(String accountId, double amount) { /* logic */ }
public void withdraw(String accountId, double amount) { /* logic */ }
// VIOLATION: Logging is mixed with business logic
public void logTransaction(String message) {
System.out.println("LOG: " + message);
}
}
Problem: The class handles both banking operations and logging.
✅ After (SRP Applied)
public class BankAccountService {
private TransactionLogger logger;
public void deposit(String accountId, double amount) {
// business logic
logger.log("Deposited: " + amount);
}
}
public class TransactionLogger {
public void log(String message) {
System.out.println("LOG: " + message);
}
}
Now: One class handles account operations, another handles logging.
💼 Banking Example Use Case
- BankAccountService → handles money operations
- TransactionLogger → logging
- NotificationService → send SMS/email
Each has a single job.
O — Open/Closed Principle (OCP)
Classes should be open for extension but closed for modification.
❌ Before (Violation)
public class LoanInterestCalculator {
public double calculateInterest(String loanType, double amount) {
if (loanType.equals("HOME")) return amount * 0.08;
if (loanType.equals("PERSONAL")) return amount * 0.13;
if (loanType.equals("CAR")) return amount * 0.09;
return 0;
}
}
Problem: Adding new loan type requires modifying the class.
✅ After (OCP Applied)
public interface InterestPolicy {
double calculate(double amount);
}
public class HomeLoanInterest implements InterestPolicy {
public double calculate(double amount) { return amount * 0.08; }
}
public class PersonalLoanInterest implements InterestPolicy {
public double calculate(double amount) { return amount * 0.13; }
}
public class LoanInterestCalculator {
public double compute(InterestPolicy policy, double amount) {
return policy.calculate(amount);
}
}
Now: Add new loan type → create new class (no modification required).
💼 Banking Example Use Case
- Home Loan
- Personal Loan
- Car Loan
- Business Loan
All follow the same interface.
L — Liskov Substitution Principle (LSP)
Subclasses should be substitutable for their base classes.
❌ Before (Violation)
public class BankCard {
public void withdraw(double amount) {}
}
public class CreditCard extends BankCard {
public void withdraw(double amount) { /* OK */ }
}
public class DebitCard extends BankCard {
public void withdraw(double amount) { /* OK */ }
}
public class PrepaidCard extends BankCard {
// VIOLATION: prepaid card cannot withdraw if top-up not done
public void withdraw(double amount) {
throw new UnsupportedOperationException("Top-up required");
}
}
Problem: A subtype is breaking the parent behavior.
✅ After (LSP Applied)
public interface WithdrawableCard {
void withdraw(double amount);
}
public interface RechargeableCard {
void recharge(double amount);
}
public class CreditCard implements WithdrawableCard { /* ... */ }
public class DebitCard implements WithdrawableCard { /* ... */ }
public class PrepaidCard implements RechargeableCard { /* ... */ }
Now: Types are replaced with proper interfaces based on behavior.
💼 Banking Example Use Case
- CreditCard, DebitCard → Withdraw functionality
- PrepaidCard → Recharge functionality
Avoid forcing features that certain cards do not support.
I — Interface Segregation Principle (ISP)
Clients should not be forced to implement methods they don’t use.
❌ Before (Violation)
public interface BankOperations {
void deposit();
void withdraw();
void getLoan();
void calculateInterest();
}
public class FixedDepositService implements BankOperations {
public void deposit() {}
public void withdraw() { throw new UnsupportedOperationException(); }
public void getLoan() { throw new UnsupportedOperationException(); }
public void calculateInterest() {}
}
Problem: FD account does not support withdraw or loans.
✅ After (ISP Applied)
public interface DepositAccount {
void deposit();
}
public interface InterestBearing {
void calculateInterest();
}
public class FixedDepositService implements DepositAccount, InterestBearing {
public void deposit() {}
public void calculateInterest() {}
}
💼 Banking Example Use Case
- Savings account uses: deposit, withdraw
- Fixed Deposit uses: deposit, interest
- Loan account uses: loan, EMI calculation
Each gets its own interface.
D — Dependency Inversion Principle (DIP)
High-level modules should depend on abstractions, not concrete classes.
❌ Before (Violation)
public class PaymentService {
private HdfcBankGateway gateway = new HdfcBankGateway();
public void sendPayment(double amount) {
gateway.transfer(amount);
}
}
Problem: Hard-coded dependency → cannot switch to SBI/ICICI.
✅ After (DIP Applied)
public interface BankGateway {
void transfer(double amount);
}
public class HdfcBankGateway implements BankGateway {
public void transfer(double amount) {}
}
public class PaymentService {
private BankGateway gateway;
public PaymentService(BankGateway gateway) {
this.gateway = gateway;
}
public void sendPayment(double amount) {
gateway.transfer(amount);
}
}
Now: High-level class depends on interface, not implementation.
💼 Banking Example Use Case
- PaymentService works with any bank gateway (SBI, ICICI, HDFC, etc.)
- Easy to extend → plug new banks
📌 Summary Table
| Principle | Meaning | Banking Example |
|---|---|---|
| SRP | One class = one responsibility | AccountService vs Logger |
| OCP | Add new behavior without modifying existing code | New loan types |
| LSP | Subtypes must behave like base types | Card types |
| ISP | Small specialized interfaces | FD, Loan, Savings accounts |
| DIP | Depend on abstractions | Bank Gateway Interface |
📘 Notes / Key Takeaways
- Follow SOLID to avoid tightly coupled code.
- Banking apps change frequently → SOLID ensures scalability.
- Most violations occur when adding new features quickly.
- DIP + OCP are the most important for real banking systems.
- Use interfaces, avoid concrete bindings.
- Prefer compo