SOLID принципы в Java: теория и практика от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

SOLID принципы в Java: от теории к практике

SOLID — это акроним пяти фундаментальных принципов объектно-ориентированного программирования, сформулированных Робертом Мартином (дядюшкой Бобом). Эти принципы помогают создавать гибкий, поддерживаемый и масштабируемый код. В этой статье мы разберем каждый принцип на примерах Java, покажем, как их нарушают, и как правильно применять.

Зачем нужны SOLID принципы?

Без SOLID код быстро превращается в «спагетти». Вы сталкивались с ситуацией, когда изменение одного класса ломает три других? Или когда метод делает слишком много? SOLID решает эти проблемы. Каждый принцип — это правило, которое делает код:

  • Устойчивым к изменениям — новые требования не ломают старую логику.
  • Понятным — каждый класс отвечает за одну вещь.
  • Тестируемым — легко писать юнит-тесты.

1. Single Responsibility Principle (SRP) — Принцип единственной ответственности

Формулировка: «У класса должна быть только одна причина для изменения».

Простыми словами: класс должен делать только одну вещь. Если у класса больше одной ответственности, его сложно поддерживать.

Нарушение SRP (плохой код)

public class OrderService {
    public void createOrder(Order order) {
        // Логика создания заказа
        saveToDatabase(order);
        sendEmailConfirmation(order);
    }

    private void saveToDatabase(Order order) {
        // Сохранение в БД
    }

    private void sendEmailConfirmation(Order order) {
        // Отправка email
    }
}

Проблема: класс отвечает и за бизнес-логику, и за работу с БД, и за отправку уведомлений. Изменение email-сервиса потребует изменения OrderService.

Правильная реализация SRP

public class OrderService {
    private final OrderRepository repository;
    private final EmailService emailService;

    public OrderService(OrderRepository repo, EmailService email) {
        this.repository = repo;
        this.emailService = email;
    }

    public void createOrder(Order order) {
        repository.save(order);
        emailService.sendConfirmation(order);
    }
}

public class OrderRepository {
    public void save(Order order) { /* логика БД */ }
}

public class EmailService {
    public void sendConfirmation(Order order) { /* логика email */ }
}

Теперь каждый класс имеет одну причину для изменения. OrderService меняется только при изменении бизнес-процесса создания заказа.

2. Open/Closed Principle (OCP) — Принцип открытости/закрытости

Формулировка: «Классы должны быть открыты для расширения, но закрыты для модификации».

Это означает, что вы должны добавлять новую функциональность через новый код, а не изменять существующий.

Нарушение OCP (плохой код)

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

Чтобы добавить треугольник, нужно изменить метод calculateArea — это нарушает OCP.

Правильная реализация OCP

public interface Shape {
    double calculateArea();
}

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

public class Circle implements Shape {
    private double radius;
    public Circle(double r) { this.radius = r; }
    @Override
    public double calculateArea() { return Math.PI * radius * radius; }
}

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

Теперь для добавления новой фигуры достаточно создать класс, реализующий Shape. Существующий код не меняется.

3. Liskov Substitution Principle (LSP) — Принцип подстановки Барбары Лисков

Формулировка: «Объекты подкласса должны быть взаимозаменяемы с объектами суперкласса без нарушения корректности программы».

Если у вас есть класс Bird и подкласс Penguin, то код, работающий с Bird, должен корректно работать и с Penguin.

Нарушение LSP (плохой код)

public class Bird {
    public void fly() { System.out.println("Flying"); }
}

public class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly!");
    }
}

// Клиентский код
public void makeBirdFly(Bird bird) {
    bird.fly(); // Упадет с ошибкой для Penguin
}

Пингвин — птица, но не летает. Наследование здесь нарушает LSP.

Правильная реализация LSP

public abstract class Bird {
    public abstract void move();
}

public class FlyingBird extends Bird {
    @Override
    public void move() { System.out.println("Flying"); }
}

public class Penguin extends Bird {
    @Override
    public void move() { System.out.println("Swimming"); }
}

// Клиентский код
public void makeBirdMove(Bird bird) {
    bird.move(); // Работает для всех птиц
}

Теперь подклассы корректно заменяют базовый класс.

4. Interface Segregation Principle (ISP) — Принцип разделения интерфейсов

Формулировка: «Клиенты не должны зависеть от методов, которые они не используют».

Лучше иметь несколько узкоспециализированных интерфейсов, чем один «толстый».

Нарушение ISP (плохой код)

public interface Worker {
    void work();
    void eat();
}

public class Robot implements Worker {
    @Override
    public void work() { System.out.println("Working"); }
    @Override
    public void eat() { throw new UnsupportedOperationException("Robots don't eat"); }
}

Робот вынужден реализовывать метод eat(), который ему не нужен.

Правильная реализация ISP

public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public class Human implements Workable, Eatable {
    @Override
    public void work() { System.out.println("Working"); }
    @Override
    public void eat() { System.out.println("Eating"); }
}

public class Robot implements Workable {
    @Override
    public void work                

Блоги

Рекомендаций по книгам