🚀 SOLID Principles - Notes! 🔥
Anonymous User
2186

Solid Principles

  • To enhance the maintainability, scalability, and robustness of object-oriented software
  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

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);
    }
}
Comments (3)