SOLID Principles in Java: Theory and Practice for Developers

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 clean code



Every Java developer sooner or later faces the problem of "dirty" code: classes that do everything at once, rigid dependencies, and the inability to extend without breaking existing logic. This is where SOLID principles come to the rescue — five fundamental rules of object-oriented design formulated by Robert Martin (Uncle Bob). In this article, we will break down each principle in practice with Java examples, so your code becomes flexible, testable, and easy to maintain.



1. Single Responsibility Principle (SRP)



Essence: "A class should have only one reason to change." In other words, a class should be responsible for one clearly defined task.



SRP Violation

Consider the OrderService class, which simultaneously handles price calculation, database saving, and email sending:



public class OrderService {    public void processOrder(Order order) {        // 1. Price calculation        double total = order.getItems().stream()                .mapToDouble(Item::getPrice)                .sum();

// 2. Saving to database Database.save(order);

// 3. Sending notification EmailService.send("Order placed for amount: " + total); }}


Problem: Changing the email sending logic or the saving method will force changes to OrderService. This violates SRP.



Refactoring by SRP

We split the responsibilities into three separate classes:



public class PriceCalculator {    public double calculateTotal(Order order) {        return order.getItems().stream()                .mapToDouble(Item::getPrice)                .sum();    }}

public class OrderRepository { public void save(Order order) { Database.save(order); }}

public class NotificationService { public void sendConfirmation(Order order) { EmailService.send("Order placed for amount: " + order.getTotal()); }}

// Client codepublic class OrderProcessor { private PriceCalculator calculator; private OrderRepository repository; private NotificationService notifier;

public void processOrder(Order order) { double total = calculator.calculateTotal(order); order.setTotal(total); repository.save(order); notifier.sendConfirmation(order); }}


Result: Each class has one reason to change. The code becomes modular and testable.



2. Open-Closed Principle (OCP)



Essence: "Software entities should be open for extension, but closed for modification." New functionality is added through extension, not by modifying existing code.



OCP Violation

Imagine a discount system where each new discount type requires changing the applyDiscount method:



public class DiscountService {    public double applyDiscount(String type, double price) {        if (type.equals("SUMMER")) {            return price * 0.9;        } else if (type.equals("VIP")) {            return price * 0.8;        }        // Adding a new type → modifying the class        return price;    }}


Solution through polymorphism

We use an interface and different implementations:



public interface Discount {    double apply(double price);}

public class SummerDiscount implements Discount { @Override public double apply(double price) { return price * 0.9; }}

public class VipDiscount implements Discount { @Override public double apply(double price) { return price * 0.8; }}

// New type — just a new class

Blogs

Book Recommendations