SOLID Principles in Java: Theory and Practice from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

SOLID Principles in Java: From Theory to Practice

\n\n

SOLID is an acronym for five fundamental principles of object-oriented programming, formulated by Robert Martin (Uncle Bob). These principles help create flexible, maintainable, and scalable code. In this article, we will break down each principle using Java examples, show how they are violated, and how to apply them correctly.

\n\n

Why are SOLID principles needed?

\n

Without SOLID, code quickly turns into "spaghetti." Have you encountered a situation where changing one class breaks three others? Or when a method does too much? SOLID solves these problems. Each principle is a rule that makes code:

\n
    \n
  • Resistant to change — new requirements do not break old logic.
  • \n
  • Understandable — each class is responsible for one thing.
  • \n
  • Testable — easy to write unit tests.
  • \n
\n\n

1. Single Responsibility Principle (SRP)

\n

Definition: "A class should have only one reason to change."

\n

In simple words: a class should do only one thing. If a class has more than one responsibility, it is difficult to maintain.

\n\n

SRP Violation (Bad Code)

\n
public class OrderService {\n    public void createOrder(Order order) {\n        // Order creation logic\n        saveToDatabase(order);\n        sendEmailConfirmation(order);\n    }\n\n    private void saveToDatabase(Order order) {\n        // Saving to DB\n    }\n\n    private void sendEmailConfirmation(Order order) {\n        // Sending email\n    }\n}
\n

Problem: the class is responsible for business logic, database operations, and sending notifications. Changing the email service would require changing OrderService.

\n\n

Correct SRP Implementation

\n
public class OrderService {\n    private final OrderRepository repository;\n    private final EmailService emailService;\n\n    public OrderService(OrderRepository repo, EmailService email) {\n        this.repository = repo;\n        this.emailService = email;\n    }\n\n    public void createOrder(Order order) {\n        repository.save(order);\n        emailService.sendConfirmation(order);\n    }\n}\n\npublic class OrderRepository {\n    public void save(Order order) { /* DB logic */ }\n}\n\npublic class EmailService {\n    public void sendConfirmation(Order order) { /* email logic */ }\n}
\n

Now each class has one reason to change. OrderService changes only when the order creation business process changes.

\n\n

2. Open/Closed Principle (OCP)

\n

Definition: "Classes should be open for extension but closed for modification."

\n

This means you should add new functionality through new code, not by modifying existing code.

\n\n

OCP Violation (Bad Code)

\n
public class AreaCalculator {\n    public double calculateArea(Object shape) {\n        if (shape instanceof Rectangle) {\n            Rectangle r = (Rectangle) shape;\n            return r.width * r.height;\n        } else if (shape instanceof Circle) {\n            Circle c = (Circle) shape;\n            return Math.PI * c.radius * c.radius;\n        }\n        throw new IllegalArgumentException("Unknown shape");\n    }\n}
\n

To add a triangle, you need to modify the calculateArea method — this violates OCP.

\n\n

Correct OCP Implementation

\n
public interface Shape {\n    double calculateArea();\n}\n\npublic class Rectangle implements Shape {\n    private double width, height;\n    public Rectangle(double w, double h) { this.width = w; this.height = h; }\n    @Override\n    public double calculateArea() { return width * height; }\n}\n\npublic class Circle implements Shape {\n    private double radius;\n                

Blogs

Book Recommendations