SOLID Principles in Java: Theory and Practice for Clean Code

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

Introduction: Why SOLID is Important for a Java Developer



In the world of object-oriented programming, maintaining and extending code becomes increasingly complex over time. Without a clear architecture, even a small project turns into a "big ball of mud," where changing one function breaks ten others. This is where SOLID principles come to the rescue — five fundamental rules of class design formulated by Robert Martin (Uncle Bob).



For a Java developer, SOLID is not just theory, but a practical tool. Java, as a strongly typed language with a powerful system of interfaces and inheritance, is ideally suited for implementing these principles. In this article, we will break down each principle from a theoretical perspective and show live Java code examples that can be immediately applied in a project.



1. Single Responsibility Principle



Theory

SRP states: a class should have only one reason to change. In other words, a class should be responsible for one, clearly defined functionality. If a class does too much, it becomes difficult to test, modify, and reuse.



Practice in Java

Consider a typical SRP violation — a class that simultaneously manages user data and sends notifications:



// ❌ SRP Violationpublic class User {    private String name;    private String email;

public void saveToDatabase() { // Saving to DB }

public void sendWelcomeEmail() { // Sending email }}


The correct solution is to separate the responsibility into two classes:



// ✅ SRP Compliancepublic class User {    private String name;    private String email;    // Getters and setters}

public class UserRepository { public void save(User user) { // Saving to DB }}

public class EmailService { public void sendWelcomeEmail(User user) { // Sending email }}


Conclusion: each class now has one reason to change. User changes only when the data structure changes, UserRepository changes when the DB logic changes, EmailService changes when email templates change.



2. Open-Closed Principle



Theory

OCP states: classes should be open for extension, but closed for modification. This means new functionality should be added through inheritance or composition, without touching already tested code.



Practice in Java

Imagine you have a discount calculator for different customer types:



// ❌ OCP Violationpublic class DiscountCalculator {    public double calculateDiscount(String customerType, double amount) {        if (customerType.equals("Regular")) {            return amount * 0.1;        } else if (customerType.equals("Premium")) {            return amount * 0.2;        }        return 0;    }}


When adding a new customer type (e.g., "Gold"), you would have to modify the existing class. The solution is to use polymorphism:



// ✅ OCP Compliancepublic interface DiscountStrategy {    double calculate(double amount);}

public class RegularDiscount implements DiscountStrategy { @Override public double calculate(double amount) { return amount * 0.1; }}

public class PremiumDiscount implements DiscountStrategy { @Override public double calculate(double amount) { return amount * 0.2; }}

public class DiscountCalculator { private DiscountStrategy strategy;

public DiscountCalculator(DiscountStrategy strategy) { this.strategy = strategy; }

public double calculate(double amount) {

Blogs

Book Recommendations