Fundamentals 7 min read

Understanding SOLID Principles with Java Examples

This article explains the five SOLID design principles—SRP, OCP, LSP, ISP, and DIP—detailing their definitions, common violations, and Java code examples that demonstrate how to refactor classes for better modularity, extensibility, and maintainability.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Understanding SOLID Principles with Java Examples

The SOLID principles are a carefully crafted set of guidelines that help developers build software architectures with higher maintainability, readability, and flexibility. They were introduced by industry legend Robert C. Martin ("Uncle Bob"). Below we analyze each principle with concrete Java examples.

1. Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change, focusing on a single functionality or responsibility. This promotes high cohesion and low coupling, improving readability, maintainability, and extensibility.

Example of violation: A class that handles both user authentication and data storage.

public class User {
    public void login(String username, String password) {
        // authentication logic
    }

    public void saveUserDetails(String userDetails) {
        // save user details to database
    }
}

The above class violates SRP because it has two responsibilities. It can be refactored into two separate classes:

public class User {
    public void login(String username, String password) {
        // authentication logic
    }
}

public class UserRepository {
    public void saveUserDetails(String userDetails) {
        // save user details to database
    }
}

2. Open/Closed Principle (OCP)

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

Violation example: An AreaCalculator that directly computes the area of a rectangle.

public class AreaCalculator {
    public double calculateRectangleArea(Rectangle rectangle) {
        return rectangle.getWidth() * rectangle.getHeight();
    }
}

Adding a new shape would require modifying this class, breaking OCP. Using polymorphism solves the problem:

public interface Shape {
    double calculateArea();
}

public class Rectangle implements Shape {
    private double width;
    private double height;
    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();
    }
}

Now AreaCalculator is closed for modification but open for extension via new Shape implementations.

3. Liskov Substitution Principle (LSP)

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

Violation example: A Penguin class that extends Bird but throws an exception in fly() .

public class Bird {
    public void fly() {
        // flying logic
    }
}

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

This violates LSP because a Penguin cannot be used wherever a Bird is expected. A better design separates the flying capability:

public class Bird {}

public class FlyingBird extends Bird {
    public void fly() {
        // flying logic
    }
}

public class Penguin extends Bird {}

4. Interface Segregation Principle (ISP)

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

Violation example: A monolithic Printer interface that includes print, scan, and fax methods.

public interface Printer {
    void print();
    void scan();
    void fax();
}

A simple printer that only prints would still need to implement scan and fax , violating ISP. The interfaces can be split:

public interface Printer {
    void print();
}

public interface Scanner {
    void scan();
}

public interface Fax {
    void fax();
}

Now a basic printer only implements the Printer interface.

5. Dependency Inversion Principle (DIP)

Definition: 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.

Violation example: UserService directly creates and uses a MySQLDatabase instance.

public class MySQLDatabase {
    public void connect() {
        // connection logic
    }
}

public class UserService {
    private MySQLDatabase database = new MySQLDatabase();
    public void saveUser(String user) {
        database.connect();
        // save user logic
    }
}

Refactoring with an abstraction removes the direct dependency:

public interface Database {
    void connect();
}

public class MySQLDatabase implements Database {
    @Override
    public void connect() {
        // connection logic
    }
}

public class UserService {
    private Database database;
    public UserService(Database database) {
        this.database = database;
    }
    public void saveUser(String user) {
        database.connect();
        // save user logic
    }
}

Now UserService depends on the Database abstraction, adhering to DIP.

Conclusion

The SOLID principles provide a solid foundation for building modular, extensible, and highly maintainable software architectures. By following these guidelines, developers can produce clearer, more testable, and easier‑to‑extend code, significantly improving development efficiency and quality.

Javasoftware architectureDesign Principlesobject-orientedSOLID
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.