SOLID Principles
1. S — Single Responsibility Principle (SRP)
👉 A class should have only one reason to change.
This class does too much → payment + receipt + notification.
void makePayment() { }
}
void generateReceipt() { }
}
void sendNotification() { }
}
Each class has
one responsibility.
If tomorrow notification changes (
SMS →
WhatsApp), only
NotificationService
changes.
2. O — Open/Closed Principle (OCP)
👉 Classes should be open for extension but closed for modification.
void process(String type) {
if(type.equals("UPI")) { /* UPI logic */ }
else if(type.equals("CARD")) { /* Card logic */ } else if(type.equals("WALLET")) { /* Wallet logic */ } }
}
If a new method like NETBANKING
comes, you must modify this class → breaks OCP.
void pay();
}
class UPIPayment implements PaymentMethod {
public void pay() { /* UPI logic */ }
}
public void pay() { /* Card logic */ }
}
class PaymentProcessor {
private PaymentMethod method;
PaymentProcessor(PaymentMethod method) { this.method = method; }
void process() {
method.pay();
}
}
Now adding
NetBanking means just creating a new class →
extend, not modify.
3. L — Liskov Substitution Principle (LSP)
👉 Subclasses should be substitutable for their base classes without breaking the program.
LSP Rule in One Line
If you replace a parent class with a child class, your code should still work fine.
Means: Child class should behave like parent class without breaking anything.
🍽️ Real-Life Analogy: Spoon vs Fork
You have a function that eats soup using a Spoon
.
You create a subclass Fork
and pass it to the same function.
But Fork
can’t hold soup — so the function fails.
That’s a violation of LSP.
class PaymentMethod {
void pay() { }
}
void pay() {
}
}
Here, if you use CODPayment
instead of UPIPayment
, it breaks things.
interface PaymentMethod {
void pay();
}
class UPIPayment implements PaymentMethod {
public void pay() { /* pay via UPI */ }
}
class CardPayment implements PaymentMethod {
public void pay() { /* pay via Card */ }
}
Every subclass truly works as PaymentMethod.
4. I — Interface Segregation Principle (ISP)
👉 No client should be forced to depend on methods it doesn’t use.
interface PaymentService {
void pay();
void refund();
void emiConversion();
}
Now, UPIPayment doesn’t support emiConversion
, but it’s forced to implement it.
Good Example
interface Payment {
void pay();
}
void refund();
}
void emiConversion();
}
class UPIPayment implements Payment {
public void pay() { /* UPI logic */ }
}
class CardPayment implements Payment, EMIConvertible {
public void pay() { /* card logic */ }
public void emiConversion() { /* EMI logic */ }
}
Each class implements only what it needs.
5. D — Dependency Inversion Principle (DIP)
👉 High-level modules should not depend on low-level modules. Both should depend on abstractions.
🔑 Definition
High-level modules should not depend on low-level modules. Both should depend on abstractions.
-
High-level module = big boss logic (e.g. PaymentProcessor
that handles checkout).
-
Low-level module = detail logic (e.g. UPIPayment
, CardPayment
, etc.).
-
Abstraction = an interface or abstract class (e.g. PaymentMethod
).
❌ Without Dependency Inversion (bad design)
// Low-level modules
class UPIPayment {
void pay() {
System.out.println("Paying via UPI");
}
}
class WalletPayment {
void pay() {
System.out.println("Paying via Wallet");
}
}
class CardPayment {
void pay() {
System.out.println("Paying via Card");
}
}
// High-level module
class PaymentProcessor {
private UPIPayment upi = new UPIPayment();
private WalletPayment wallet = new WalletPayment();
private CardPayment card = new CardPayment();
void process(String type) {
if (type.equals("UPI")) {
upi.pay();
} else if (type.equals("Wallet")) {
wallet.pay();
} else if (type.equals("Card")) {
card.pay();
} else {
System.out.println("Invalid payment method");
}
}
}
// Test app
public class Main {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor();
processor.process("UPI");
processor.process("Wallet");
processor.process("Card");
}
}
🚨 Problems here:
-
Tightly coupled
-
PaymentProcessor
knows all payment methods (UPI, Wallet, Card).
-
If tomorrow you add NetBanking, you must open and change PaymentProcessor
.
-
Violates OCP (Open-Closed Principle) too.
-
Hard to test
You cannot replace real payment classes with mocks for testing.
-
Business logic depends on low-level details
High-level (PaymentProcessor
) is directly tied to low-level classes (UPIPayment
, WalletPayment
, CardPayment
).
👉 This breaks the Dependency Inversion Principle (DIP).
We’ll introduce a PaymentMethod interface → all payment types (UPI, Wallet, Card, etc.) will implement it.
Then, PaymentProcessor
will depend only on the abstraction, not the concrete classes.
// Abstraction (interface)
interface PaymentMethod {
void pay();
}
// Low-level modules implement the interface
class UPIPayment implements PaymentMethod {
public void pay() {
System.out.println("Paying via UPI");
}
}
class WalletPayment implements PaymentMethod {
public void pay() {
System.out.println("Paying via Wallet");
}
}
class CardPayment implements PaymentMethod {
public void pay() {
System.out.println("Paying via Card");
}
}
// High-level module (depends on abstraction, not concrete class)
class PaymentProcessor {
private PaymentMethod paymentMethod;
// Dependency Injection (pass payment method at runtime)
public PaymentProcessor(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public void process() {
paymentMethod.pay();
}
}
// Test app
public class Main {
public static void main(String[] args) {
PaymentProcessor upiProcessor = new PaymentProcessor(new UPIPayment());
upiProcessor.process();
PaymentProcessor walletProcessor = new PaymentProcessor(new WalletPayment());
walletProcessor.process();
PaymentProcessor cardProcessor = new PaymentProcessor(new CardPayment());
cardProcessor.process();
}
}
🔑 Advantages:
-
PaymentProcessor
is not tightly coupled to UPI, Wallet, or Card.
-
Adding new method (e.g., NetBanking, Crypto, PayLater) = No change in PaymentProcessor
.
Just create a new class implementing PaymentMethod
.
-
Easy to test → you can inject a mock payment class in unit tests.
Comments
Post a Comment