Understanding SOLID Principles

Back to Index

The SOLID principles are a set of guidelines that help developers design robust, maintainable, and scalable software systems. Introduced by Robert C. Martin, these principles are fundamental to object-oriented programming. Let’s explore each principle in detail:

1. Single Responsibility Principle (SRP)

A class should have one, and only one, reason to change. This means a class should only have one job or responsibility.

Example:
class Invoice {
    void calculateTotal() {
        // Calculation logic
    }
    void printInvoice() {
        // Printing logic
    }
}
// This violates SRP. The printing responsibility should be in another class.
            

2. Open/Closed Principle (OCP)

Software entities (classes, modules, functions) should be open for extension but closed for modification.

Example:
interface Shape {
    double area();
}
class Circle implements Shape {
    double area() {
        // Calculate area of circle
    }
}
class Rectangle implements Shape {
    double area() {
        // Calculate area of rectangle
    }
}
// New shapes can be added without modifying existing code.
            

3. Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

Example:
class Bird {
    void fly() {
        // Flying logic
    }
}
class Penguin extends Bird {
    void fly() {
        throw new UnsupportedOperationException();
    }
}
// This violates LSP. Penguin is not substitutable for Bird.
            

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use.

Example:
interface Animal {
    void eat();
    void fly(); // Violates ISP, as not all animals can fly.
}
interface Eatable {
    void eat();
}
interface Flyable {
    void fly();
}
// Segregating interfaces resolves the issue.
            

5. Dependency Inversion Principle (DIP)

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

Example:
interface Database {
    void save(String data);
}
class MySQLDatabase implements Database {
    void save(String data) {
        // Save to MySQL
    }
}
class Application {
    Database database;
    void storeData(String data) {
        database.save(data);
    }
}
// Application depends on abstraction, not implementation.
            

Back to Index