Skip to main content
Dat 2nd Sem Fall 2025
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

SOLID

The SOLID design principles are a set of guidelines for object-oriented software design (OOD) intended to make software systems more understandable, flexible, and maintainable. Coined by Robert C. Martin (also known as Uncle Bob) and popularized in the early 2000s, SOLID stands for:

  1. S - Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should have only one job or responsibility. This principle aims to reduce complexity by ensuring that a class is focused on a single aspect of the system. When a class is charged with multiple responsibilities, changes in one part of its functionality may affect other unrelated aspects, leading to a system that is harder to understand and maintain.

  2. O - Open/Closed Principle (OCP): Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that the behavior of a module can be extended without modifying its source code. Achieving this typically involves interfaces or abstract classes, allowing new functionalities to be added with minimal changes to the existing code. This principle encourages the use of a stable base that can grow and change with the needs of the system.

  3. L - Liskov Substitution Principle (LSP): Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, subclasses should extend the behavior of their superclass without changing its original functionality. This principle emphasizes the need for consistency in design, ensuring that derived classes only augment and do not alter the behavior or expectations of their base classes.

  4. I - Interface Segregation Principle (ISP): Clients should not be forced to depend upon interfaces they do not use. This principle suggests that it’s better to have many specific interfaces rather than a single, general-purpose interface. By keeping interfaces small and focused, we can ensure that implementing classes don’t need to depend on methods they do not use, which leads to a cleaner, more modular design.

  5. D - Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions. Furthermore, abstractions should not depend upon details; details should depend upon abstractions. This principle aims to reduce the coupling between different parts of a system, making it more resilient to change. By depending on abstractions rather than concrete classes, software components become more decoupled and easier to modify or replace.

Together, the SOLID principles guide developers in creating systems that are easier to maintain, scale, and understand by promoting a design that is modular, flexible, and cohesive. Following these principles helps in avoiding code smells, refactoring code, and developing software that is more robust and adaptable to change.

Lets look at some code examples

Single Responsibility Principle (SRP)

// Bad Example
class User {
    private String name;
    private String email;

    public void sendEmail(String message) {
        // Code to send email
    }
}

// Good Example
class User {
    private String name;
    private String email;
}

class EmailService {
    public void sendEmail(String to, String message) {
        // Code to send email
    }
}
  • SRP is often misunderstood as “one method per class” or “make everything tiny and separated.” But it’s not about splitting everything apart—it’s about ensuring that a class has only one reason to change.

Open/Closed Principle (OCP)

// Bad Example because it requires modification of the Rectangle class to add new shapes
class Rectangle {
    public double width;
    public double height;
}

class AreaCalculator {
    public double calculateArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }
}

// Good Example
abstract class Shape {
    public abstract double area();
}

class Rectangle extends Shape {
    public double width;
    public double height;

    @Override
    public double area() {
        return width * height;
    }
}

class Circle extends Shape {
    public double radius;

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.area();
    }
}
  • OCP is about designing your system so that you can add new functionality without changing existing code. This often involves using interfaces or abstract classes.

Liskov Substitution Principle (LSP)

// Bad Example because it assumes all birds can fly
class Bird {
    public void fly() {
        // Flying logic
    }
}

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

// Good Example
class Bird {
    public void move() {
        // General movement logic
    }
}

class Sparrow extends Bird {
    @Override
    public void move() {
        // Flying logic
    }
}

class Ostrich extends Bird {
    @Override
    public void move() {
        // Walking logic
    }
}
  • LSP is about ensuring that subclasses can stand in for their parent classes without causing issues. This means that if a class is using a parent class reference, it should be able to use any subclass without knowing it.

Interface Segregation Principle (ISP)

// Bad Example because it forces all workers to implement all methods
interface Worker {
    void work();
    void eat();
}

class Human implements Worker {
    @Override
    public void work() {
        // Working logic
    }

    @Override
    public void eat() {
        // Eating logic
    }
}

class Robot implements Worker {
    @Override
    public void work() {
        // Working logic
    }

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

// Good Example
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Human implements Workable, Eatable {
    @Override
    public void work() {
        // Working logic
    }

    @Override
    public void eat() {
        // Eating logic
    }
}

class Robot implements Workable {
    @Override
    public void work() {
        // Working logic
    }
}
  • ISP is about ensuring that no client is forced to depend on methods it does not use. This means creating smaller, more specific interfaces rather than a large, general-purpose one.

Dependency Inversion Principle (DIP)

// Bad Example because it depends on a concrete implementation
class LightBulb {
    public void turnOn() {
        // Turning on the light bulb
    }
}

class Switch {
    private LightBulb lightBulb;

    public Switch(LightBulb lightBulb) {
        this.lightBulb = lightBulb;
    }

    public void operate() {
        lightBulb.turnOn();
    }
}
// Good Example because it depends on an abstraction
interface Switchable {
    void turnOn();
}

class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        // Turning on the light bulb
    }
}

class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate() {
        device.turnOn();
    }
}
  • DIP is about ensuring that high-level modules do not depend on low-level modules but rather on abstractions. This means using interfaces or abstract classes to decouple components.

Back to Design Pattern overview