SOLID Principles

The SOLID principles are a set of design guidelines that help developers create maintainable, scalable, and robust software. These principles are essential in Object-Oriented Programming (OOP) and system design, making code easier to understand and modify.

What Does SOLID Stand For?

  1. S: Single Responsibility Principle (SRP)
  2. O: Open/Closed Principle (OCP)
  3. L: Liskov Substitution Principle (LSP)
  4. I: Interface Segregation Principle (ISP)
  5. D: Dependency Inversion Principle (DIP)

Each principle addresses specific design challenges and ensures a clean, modular codebase.

Single Responsibility Principle (SRP)

Definition

A class should have only one reason to change. This means each class should focus on one task or responsibility.

Why It’s Important

  • Reduces complexity.
  • Makes the class easier to test and maintain.

Example

Bad Design

				
					class Employee {
    private String name;
    private String role;

    public Employee(String name, String role) {
        this.name = name;
        this.role = role;
    }

    public void calculateSalary() {
        // Logic to calculate salary
        System.out.println("Calculating salary for " + name);
    }

    public void generateReport() {
        // Logic to generate a report
        System.out.println("Generating report for " + name);
    }
}

				
			

Here, the Employee class is handling multiple responsibilities: salary calculation and report generation. This violates SRP. 

Now suppose if I have to change calculateSalary() and generateReport() together then it violates the rule , you should change class only for a single reason or just one change you should allow in a class 

Good Design

				
					class Employee {
    private String name;
    private String role;

    public Employee(String name, String role) {
        this.name = name;
        this.role = role;
    }

    public String getName() {
        return name;
    }

    public String getRole() {
        return role;
    }
}

class SalaryCalculator {
    public void calculateSalary(Employee employee) {
        System.out.println("Calculating salary for " + employee.getName());
    }
}

class ReportGenerator {
    public void generateReport(Employee employee) {
        System.out.println("Generating report for " + employee.getName());
    }
}

				
			

Explanation

  • The Employee class now focuses only on representing an employee.
  • Separate classes (SalaryCalculator and ReportGenerator) handle their respective responsibilities.

Open/Closed Principle (OCP)

Definition

Classes should be open for extension but closed for modification. This means you can add new functionality without changing existing code.

Why It’s Important

  • Prevents breaking existing functionality.
  • Makes the system easier to extend.

Example

Bad Design

				
					class PaymentProcessor {
    public void processPayment(String paymentType) {
        if (paymentType.equals("CreditCard")) {
            System.out.println("Processing credit card payment");
        } else if (paymentType.equals("PayPal")) {
            System.out.println("Processing PayPal payment");
        }
    }
}

				
			

Adding a new payment method requires modifying the processPayment method, violating OCP.

Good Design

				
					interface PaymentMethod {
    void processPayment();
}

class CreditCardPayment implements PaymentMethod {
    @Override
    public void processPayment() {
        System.out.println("Processing credit card payment");
    }
}

class PayPalPayment implements PaymentMethod {
    @Override
    public void processPayment() {
        System.out.println("Processing PayPal payment");
    }
}

class PaymentProcessor {
    public void processPayment(PaymentMethod paymentMethod) {
        paymentMethod.processPayment();
    }
}

				
			

Explanation

  • Adding a new payment method (e.g., BitcoinPayment) only requires creating a new class that implements PaymentMethod.
  • No existing code needs to be changed.

Liskov Substitution Principle (LSP)

Definition

A subclass should be substitutable for its superclass without altering the correctness of the program.

Why It’s Important

  • Ensures compatibility between base and derived classes.
  • Prevents unexpected behavior.

Example

Bad Design

				
					class Bird {
    public void fly() {
        System.out.println("Flying");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly");
    }
}

				
			

Here, substituting Penguin for Bird would cause errors, violating LSP.

Good Design

				
					class Bird {
}

class FlyingBird extends Bird {
    public void fly() {
        System.out.println("Flying");
    }
}

class Penguin extends Bird {
    public void swim() {
        System.out.println("Swimming");
    }
}

				
			

Explanation

  • FlyingBird is a separate class for birds that can fly.
  • Penguin no longer inherits the fly behavior, adhering to LSP.

Interface Segregation Principle (ISP)

Definition

A class should not be forced to implement methods it does not use.

Why It’s Important

  • Prevents unnecessary dependencies.
  • Keeps interfaces focused and specific.

Example

Bad Design

				
					interface Worker {
    void work();
    void eat();
}

class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("Working");
    }

    @Override
    public void eat() {
        throw new UnsupportedOperationException("Robots don't eat");
    }
}

				
			

Good Design

				
					interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Human implements Workable, Eatable {
    @Override
    public void work() {
        System.out.println("Working");
    }

    @Override
    public void eat() {
        System.out.println("Eating");
    }
}

class Robot implements Workable {
    @Override
    public void work() {
        System.out.println("Working");
    }
}

				
			

Explanation

  • Separate interfaces (Workable and Eatable) ensure that classes only implement what they need.

Dependency Inversion Principle (DIP)

Definition

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Why It’s Important

  • Reduces coupling between classes.
  • Makes the system more flexible.

Example

Bad Design

				
					class MySQLDatabase {
    public void connect() {
        System.out.println("Connecting to MySQL database");
    }
}

class Application {
    private MySQLDatabase database;

    public Application() {
        this.database = new MySQLDatabase();
    }

    public void start() {
        database.connect();
    }
}

				
			

Good Design

				
					interface Database {
    void connect();
}

class MySQLDatabase implements Database {
    @Override
    public void connect() {
        System.out.println("Connecting to MySQL database");
    }
}

class PostgreSQLDatabase implements Database {
    @Override
    public void connect() {
        System.out.println("Connecting to PostgreSQL database");
    }
}

class Application {
    private Database database;

    public Application(Database database) {
        this.database = database;
    }

    public void start() {
        database.connect();
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Database database = new MySQLDatabase();
        Application app = new Application(database);
        app.start();
    }
}

				
			

Explanation

  • The Application class depends on the Database abstraction, allowing the database implementation to be swapped easily.

The SOLID principles are foundational for creating clean and maintainable code. By applying these principles, you can build robust, scalable, and maintainable systems that are easy to extend and modify. Happy coding ! ❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India