← Назад к вопросам

Что такое SOLID dependency injection?

2.0 Middle🔥 211 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SOLID и Dependency Injection: основы чистого кода

Dependency Injection (DI) и SOLID принципы — это фундаментальные концепции в modern Java разработке. Они работают вместе для создания гибкого, тестируемого и поддерживаемого кода.

Что такое Dependency (зависимость)

// ❌ Плохо: жёсткая зависимость (tight coupling)
public class UserService {
    private EmailService emailService = new EmailService();  // создаём сами!
    
    public void registerUser(User user) {
        // ... логика регистрации ...
        emailService.sendWelcomeEmail(user.getEmail());  // привязаны к EmailService
    }
}

// Проблемы:
// - Невозможно тестировать UserService без реального EmailService
// - Если нужен другой EmailService (SMS, Firebase и т.д.), переписываем класс
// - Трудно менять реализацию в runtime

// ✅ Хорошо: Dependency Injection (инъекция зависимостей)
public class UserService {
    private NotificationService notificationService;  // интерфейс, не реализация!
    
    // Зависимость передаётся снаружи (injection)
    public UserService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
    
    public void registerUser(User user) {
        // ... логика регистрации ...
        notificationService.sendNotification(user.getEmail());  // используем интерфейс
    }
}

// Преимущества:
// - Легко тестировать с mock'ом
// - Легко менять реализацию
// - Гибкая архитектура

Три типа Dependency Injection

1. Constructor Injection (рекомендуется)

public class UserService {
    private final EmailService emailService;
    private final UserRepository userRepository;
    
    // Зависимости явно указаны в конструкторе
    public UserService(EmailService emailService, UserRepository userRepository) {
        this.emailService = emailService;
        this.userRepository = userRepository;
    }
}

// Преимущества:
// + Явная иммутабельность (final)
// + Все зависимости видны сразу
// + Легко тестировать
// + Нет NullPointerException
// + Spring автоматически resolve'ит зависимости

2. Setter Injection

public class UserService {
    private EmailService emailService;
    
    // Зависимость устанавливается через setter
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

// Проблемы:
// - Зависимость необязательна (может быть null)
// - Объект в неполном состоянии после создания
// - Трудно отследить все зависимости
// - Может привести к NullPointerException

// Spring конфигурация:
@Configuration
public class AppConfig {
    @Bean
    public UserService userService(EmailService emailService) {
        UserService service = new UserService();
        service.setEmailService(emailService);
        return service;
    }
}

3. Interface/Field Injection

@Service
public class UserService {
    @Autowired  // Spring inject'ит зависимость в поле
    private EmailService emailService;
}

// Проблемы:
// - Скрытые зависимости (не видны в конструкторе)
// - Невозможно создать объект без Spring
// - Трудно тестировать
// - Нарушает инкапсуляцию

// ❌ Избегай этого подхода

SOLID принципы

SOLID — это аббревиатура пяти принципов для написания хорошего кода.

S — Single Responsibility Principle (SRP)

// ❌ Неправильно: один класс, много ответственности
public class UserService {
    // Регистрация пользователя
    public void registerUser(User user) { ... }
    
    // Отправка письма
    public void sendEmail(String email, String body) { ... }
    
    // Логирование
    public void log(String message) { ... }
    
    // Сохранение в БД
    public void saveUser(User user) { ... }
}

// ✅ Правильно: разделяем ответственность
public class UserService {
    private final UserRepository userRepository;
    private final NotificationService notificationService;
    private final Logger logger;
    
    public void registerUser(User user) {
        userRepository.save(user);
        notificationService.sendWelcome(user.getEmail());
        logger.info("User registered: " + user.getId());
    }
}

public class EmailService implements NotificationService {
    public void sendWelcome(String email) { ... }
}

public class UserRepository {
    public void save(User user) { ... }
}

O — Open/Closed Principle (OCP)

// ❌ Неправильно: открыт для модификации
public class PaymentProcessor {
    public void process(String paymentType, BigDecimal amount) {
        if ("CREDIT_CARD".equals(paymentType)) {
            // обработка кредитной карты
        } else if ("PAYPAL".equals(paymentType)) {
            // обработка PayPal
        } else if ("BANK_TRANSFER".equals(paymentType)) {
            // обработка банковского перевода
        }
        // Каждый раз добавляем новый else if - ПЛОХО!
    }
}

// ✅ Правильно: открыт для расширения, закрыт для модификации
public interface PaymentMethod {
    void process(BigDecimal amount);
}

public class CreditCardPayment implements PaymentMethod {
    @Override
    public void process(BigDecimal amount) {
        // обработка кредитной карты
    }
}

public class PayPalPayment implements PaymentMethod {
    @Override
    public void process(BigDecimal amount) {
        // обработка PayPal
    }
}

public class PaymentProcessor {
    private final PaymentMethod paymentMethod;
    
    public PaymentProcessor(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }
    
    public void process(BigDecimal amount) {
        paymentMethod.process(amount);  // один способ для всех
    }
}

// Чтобы добавить новый способ оплаты, создаём новый класс
// БЕЗ изменения PaymentProcessor
public class CryptoCurrencyPayment implements PaymentMethod {
    @Override
    public void process(BigDecimal amount) {
        // обработка крипто
    }
}

L — Liskov Substitution Principle (LSP)

// ❌ Неправильно: нарушаем контракт родителя
public class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Penguin extends Animal {
    @Override
    public void fly() {  // Penguin не может летать!
        throw new UnsupportedOperationException("Penguin cannot fly");
    }
}

// Проблема: если код ожидает Animal, и получит Penguin,
// всё может сломаться!

// ✅ Правильно: используем правильные абстракции
public interface Animal {
    void makeSound();
}

public interface FlyingAnimal extends Animal {
    void fly();
}

public class Dog implements Animal {
    @Override
    public void makeSound() { ... }
}

public class Bird implements FlyingAnimal {
    @Override
    public void makeSound() { ... }
    
    @Override
    public void fly() { ... }
}

public class Penguin implements Animal {
    @Override
    public void makeSound() { ... }
    // Penguin НЕ реализует FlyingAnimal - правильно!
}

I — Interface Segregation Principle (ISP)

// ❌ Неправильно: огромный интерфейс
public interface Worker {
    void work();
    void eat();
    void sleep();
    void manage();
    void report();
}

public class Robot implements Worker {
    @Override
    public void work() { ... }
    
    @Override
    public void eat() {
        throw new UnsupportedOperationException("Robot doesn't eat");
    }
    
    @Override
    public void sleep() {
        throw new UnsupportedOperationException("Robot doesn't sleep");
    }
    
    // ... и так далее
}

// ✅ Правильно: разделяем интерфейсы
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

public interface Manageable {
    void manage();
}

public class Robot implements Workable {
    @Override
    public void work() { ... }
}

public class Employee implements Workable, Eatable, Sleepable, Manageable {
    @Override
    public void work() { ... }
    
    @Override
    public void eat() { ... }
    
    // ... и так далее
}

D — Dependency Inversion Principle (DIP)

// ❌ Неправильно: зависит от конкретных классов
public class UserService {
    private MySQLDatabase database = new MySQLDatabase();  // высокоуровневый зависит от низкоуровневого!
    
    public User findUser(Long id) {
        return database.query("SELECT * FROM users WHERE id = " + id);
    }
}

public class MySQLDatabase {
    public User query(String sql) { ... }
}

// Проблема: если нужен PostgreSQL, переписываем UserService

// ✅ Правильно: зависит от абстракций
public interface UserRepository {  // абстракция
    User findById(Long id);
}

public class UserService {
    private final UserRepository repository;  // зависит от интерфейса
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public User findUser(Long id) {
        return repository.findById(id);
    }
}

// Реализации могут быть любыми
public class MySQLUserRepository implements UserRepository {
    @Override
    public User findById(Long id) { ... }
}

public class PostgresUserRepository implements UserRepository {
    @Override
    public User findById(Long id) { ... }
}

public class MongoUserRepository implements UserRepository {
    @Override
    public User findById(Long id) { ... }
}

Spring как DI контейнер

// Spring управляет созданием объектов и их зависимостями

@Repository
public class UserRepository {
    public User findById(Long id) { ... }
}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        User user = userService.findUser(id);
        return ResponseEntity.ok(UserMapper.toDTO(user));
    }
}

// Spring контейнер (ApplicationContext):
// 1. Создаёт UserRepository
// 2. Создаёт UserService, inject'ит UserRepository
// 3. Создаёт UserController, inject'ит UserService
// 4. Регистрирует Controller как REST endpoint

Тестирование с DI

@Test
void testUserService() {
    // Mock зависимость
    UserRepository mockRepository = Mockito.mock(UserRepository.class);
    
    User expectedUser = new User(1L, "Alice");
    Mockito.when(mockRepository.findById(1L))
        .thenReturn(Optional.of(expectedUser));
    
    // Inject'им mock
    UserService userService = new UserService(mockRepository);
    
    // Тестируем
    User result = userService.findUser(1L);
    
    assertEquals(expectedUser, result);
    Mockito.verify(mockRepository).findById(1L);
}

Заключение

SOLID и Dependency Injection вместе создают архитектуру, которая:

  • Гибкая — легко менять реализацию
  • Тестируемая — легко делать unit тесты
  • Поддерживаемая — легко понять и изменить код
  • Расширяемая — легко добавлять новые функции
  • Масштабируемая — архитектура не разломается при росте

Это стандарт в modern Java разработке, и Spring Boot делает это очень простым и удобным.

Что такое SOLID dependency injection? | PrepBro