Design Patterns

86. SOLID Principles

SOLID is five object-oriented design principles — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion — that guide writing maintainable, extensible Java code.

Java 21 (core language)

Explanation

S — Single Responsibility Principle: a class should have one reason to change. A class that does persistence AND formatting AND validation is hard to change any one of them safely.

O — Open/Closed Principle: open for extension (new behaviours via subclassing or composition), closed for modification (existing code need not change). Achieved with interfaces, abstract classes, and Strategy/Decorator patterns.

L — Liskov Substitution Principle: a subtype must be substitutable for its supertype without altering the correctness of the program. Violations: a Square overriding setWidth of Rectangle and silently changing height breaks callers that use the Rectangle contract.

I — Interface Segregation Principle: no client should be forced to implement methods it does not need. Split fat interfaces into smaller, focused ones.

D — Dependency Inversion Principle: high-level modules should not depend on low-level modules — both should depend on abstractions. Don't new up your collaborators; inject the interface.

Example

// S: each class has one responsibility
class ReportFormatter  { String format(String data) { return "<html>" + data + "</html>"; } }
class ReportPersistor  { void save(String report)   { System.out.println("Saved: " + report); } }

// O: open for extension (add new Shapes), closed for modification
interface AreaCalc { double area(); }
record OCircle(double r) implements AreaCalc { public double area() { return Math.PI*r*r; } }
record OSquare(double s) implements AreaCalc { public double area() { return s*s; } }

// L: subtype is fully substitutable
interface Bird { void eat(); }
interface FlyingBird extends Bird { void fly(); }
class Eagle  implements FlyingBird { public void eat(){} public void fly(){ System.out.println("Eagle flies"); } }
class Penguin implements Bird      { public void eat(){} } // doesn't implement fly — LSP OK

// I: small focused interfaces
interface Readable  { String read(); }
interface Writable  { void write(String s); }
interface ReadWrite extends Readable, Writable {} // composed when needed

// D: depend on abstraction, not concrete class
interface Logger { void log(String msg); }
class ConsoleLogger implements Logger { public void log(String msg) { System.out.println("[LOG] " + msg); } }

class OrderProcessor {
    private final Logger logger; // interface, not ConsoleLogger
    OrderProcessor(Logger logger) { this.logger = logger; }
    void process(String order) { logger.log("Processing: " + order); }
}

public class SolidPrinciples {
    public static void main(String[] args) {
        // S
        var formatter = new ReportFormatter();
        var persistor = new ReportPersistor();
        persistor.save(formatter.format("sales data"));

        // O
        for (AreaCalc shape : new AreaCalc[]{ new OCircle(5), new OSquare(4) }) {
            System.out.printf("Area: %.2f%n", shape.area());
        }

        // L
        Bird eagle = new Eagle();
        if (eagle instanceof FlyingBird fb) fb.fly();
        new Penguin().eat(); // no fly — correct

        // D
        OrderProcessor proc = new OrderProcessor(new ConsoleLogger());
        proc.process("order-001");
    }
}

Sources