Solid Principles
Examples -
1. Single Responsibility Principle
-A class should have only one reason to change, meaning it should have only a single responsibility.Scenario: ATM Machine
Imagine an ATM Machine where different operations like cash withdrawal, balance checking, and receipt printing happen.
Now, if we put all these functionalities inside a single class, it would violate SRP because one class will have multiple responsibilities.
❌ Violating SRP
class ATM {
public void withdrawCash(int amount) {
System.out.println("Withdrawing " + amount);
}
public void checkBalance() {
System.out.println("Checking balance...");
}
public void printReceipt() {
System.out.println("Printing receipt...");
}
}✅ Applying SRP
// Class responsible for withdrawing money
class CashDispenser {
public void withdrawCash(int amount) {
System.out.println("Withdrawing " + amount);
}
}
// Class responsible for balance checking
class BalanceChecker {
public void checkBalance() {
System.out.println("Checking balance...");
}
}
// Class responsible for printing receipts
class ReceiptPrinter {
public void printReceipt() {
System.out.println("Printing receipt...");
}
}
// ATM uses all these functionalities but they are separate
class ATM {
private CashDispenser cashDispenser;
private BalanceChecker balanceChecker;
private ReceiptPrinter receiptPrinter;
public ATM() {
this.cashDispenser = new CashDispenser();
this.balanceChecker = new BalanceChecker();
this.receiptPrinter = new ReceiptPrinter();
}
public void withdraw(int amount) {
cashDispenser.withdrawCash(amount);
receiptPrinter.printReceipt();
}
public void checkBalance() {
balanceChecker.checkBalance();
}
}**2. Open/Closed Principle **
-Software entities (classes, methods, modules) should be open for extension but closed for modification.❌ Violates OCP - Need to modify this class whenever a new shape is added
class InvoiceProcessor {
public double calculateTotal(String region, double amount) {
if (region.equalsIgnoreCase("India")) {
return amount + amount * 0.18;
} else if (region.equalsIgnoreCase("US")) {
return amount + amount * 0.08;
} else if (region.equalsIgnoreCase("UK")) {
return amount + amount * 0.12;
} else {
return amount; // No tax for unknown region
}
}
}
✅ Fix - Use Polymorphism
// Tax strategy Interface
interface TaxCalculator {
double calculateTax(double amount);
}
// Implementing Region-Specific Tax Calculators
class IndiaTaxCalculator implements TaxCalculator {
public double calculateTax(double amount) {
return amount * 0.18; // GST
}
}
class USTaxCalculator implements TaxCalculator {
public double calculateTax(double amount) {
return amount * 0.08; // Sales Tax
}
}
class UKTaxCalculator implements TaxCalculator {
public double calculateTax(double amount) {
return amount * 0.12; // VAT
}
}
// Using dependency Injection
class Invoice {
private double amount;
private TaxCalculator taxCalculator;
public Invoice(double amount, TaxCalculator taxCalculator) {
this.amount = amount;
this.taxCalculator = taxCalculator;
}
public double getTotalAmount() {
return amount + taxCalculator.calculateTax(amount);
}
}
// Main class
class Main {
public static void main(String[] args) {
double amount = 1000.0;
Invoice indiaInvoice = new Invoice(amount, new IndiaTaxCalculator());
System.out.println("Total (India): ₹" + indiaInvoice.getTotalAmount());
Invoice usInvoice = new Invoice(amount, new USTaxCalculator());
System.out.println("Total (US): $" + usInvoice.getTotalAmount());
Invoice ukInvoice = new Invoice(amount, new UKTaxCalculator());
System.out.println("Total (UK): £" + ukInvoice.getTotalAmount());
}
}
**3. Liskov Substitution Principle **
-Subtypes should be substitutable for their base types.
If a subclass cannot be used in place of its superclass, then it violates LSP.
❌ Violates LSP
class Bird {
public void fly() {
System.out.println("Bird is flying");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Ostriches can't fly!");
}
}✅ Fix - Use Correct Abstraction
interface Bird { }
interface FlyingBird extends Bird {
void fly();
}
class Sparrow implements FlyingBird {
@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}
class Ostrich implements Bird {
public void run() {
System.out.println("Ostrich is running");
}
}4. Interface Segregation Principle
- Clients should not be forced to depend on interfaces they do not use.
Large interfaces should be split into smaller, more specific interfaces.
❌ This violates ISP because not all printers have scan and fax capabilities.
interface MultiFunctionPrinter {
void print();
void scan();
void fax();
}
// Basic printer only supports printing but is forced to implement unused methods.
class BasicPrinter implements MultiFunctionPrinter {
@Override
public void print() {
System.out.println("Printing document...");
}
@Override
public void scan() {
throw new UnsupportedOperationException("Scan not supported!");
}
@Override
public void fax() {
throw new UnsupportedOperationException("Fax not supported!");
}
}✅ Correct Design (Following ISP)
// Separate interfaces for each functionality
interface Printer {
void print();
}
interface Scanner {
void scan();
}
interface Fax {
void fax();
}
// Basic Printer only implements what it supports
class BasicPrinter implements Printer {
@Override
public void print() {
System.out.println("Printing document...");
}
}
// Advanced Printer supports printing and scanning
class AdvancedPrinter implements Printer, Scanner {
@Override
public void print() {
System.out.println("Printing document...");
}
@Override
public void scan() {
System.out.println("Scanning document...");
}
}
// Multi-Function Printer supports print, scan, and fax
class MultiFunctionMachine implements Printer, Scanner, Fax {
@Override
public void print() {
System.out.println("Printing document...");
}
@Override
public void scan() {
System.out.println("Scanning document...");
}
@Override
public void fax() {
System.out.println("Faxing document...");
}
}5. Dependency Inversion Principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions. This promotes loose coupling.
🚨 Problem with Bad Design (Tightly Coupled)
class CreditCardPayment {
void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card.");
}
}
class PaymentService {
private CreditCardPayment paymentProcessor; // ❌ Direct dependency
public PaymentService() {
this.paymentProcessor = new CreditCardPayment(); // ❌ Tightly coupled
}
void makePayment(double amount) {
paymentProcessor.pay(amount);
}
}✅ Correct Design (Follows DIP)
// ✅ High-level module depends on an abstraction
interface PaymentProcessor {
void pay(double amount);
}
// ✅ Low-level modules depend on abstraction
class CreditCardPayment implements PaymentProcessor {
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card.");
}
}
class PayPalPayment implements PaymentProcessor {
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal.");
}
}
class UPIPayment implements PaymentProcessor {
public void pay(double amount) {
System.out.println("Paid $" + amount + " using UPI.");
}
}
// ✅ PaymentService depends on abstraction, not implementation
class PaymentService {
private PaymentProcessor paymentProcessor;
public PaymentService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
void makePayment(double amount) {
paymentProcessor.pay(amount);
}
}
//Main Function
public class Main {
public static void main(String[] args) {
PaymentProcessor creditCard = new CreditCardPayment();
PaymentService payment1 = new PaymentService(creditCard);
payment1.makePayment(100.0);
PaymentProcessor paypal = new PayPalPayment();
PaymentService payment2 = new PaymentService(paypal);
payment2.makePayment(200.0);
PaymentProcessor upi = new UPIPayment();
PaymentService payment3 = new PaymentService(upi);
payment3.makePayment(50.0);
}
}