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

Что такое принцип инъекции зависимостей?

1.2 Junior🔥 251 комментариев
#ООП

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

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

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

Принцип инъекции зависимостей (Dependency Injection)

Инъекция зависимостей (Dependency Injection, DI) — это паттерн проектирования, который помогает создавать слабосвязанный, гибкий и тестируемый код. Вместо того чтобы объект сам создавал свои зависимости, они передаются ему извне.

Основная идея

Зависимость — это объект, который требуется другому объекту для работы. Вместо создания зависимости внутри класса, мы передаём её как параметр.

❌ Без DI (Tight Coupling)
Класс A создаёт Класс B сам
  ↓
  Класс B создаёт Класс C сам
    ↓
    Класс C создаёт Класс D сам
Проблема: сильная связанность, сложно тестировать

✅ С DI (Loose Coupling)
Класс A получает Класс B от внешнего источника
Класс B получает Класс C от внешнего источника
Класс C получает Класс D от внешнего источника
Преимущество: слабая связанность, легко тестировать

Проблема без DI

// ❌ Плохо — жёсткая связанность
public class UserService {
    private UserRepository repository = new UserRepository();  // Создаём сами!
    private EmailService emailService = new EmailService();    // Создаём сами!
    
    public void createUser(User user) {
        repository.save(user);
        emailService.sendWelcomeEmail(user.getEmail());
    }
}

// Проблемы:
// 1. Сложно тестировать (нельзя использовать mock)
// 2. Если изменится конструктор EmailService — нужно менять везде
// 3. Невозможно использовать разные реализации

Решение с DI

// ✅ Хорошо — слабая связанность
public class UserService {
    private UserRepository repository;  // Зависимость
    private EmailService emailService;  // Зависимость
    
    // Конструктор получает зависимости извне
    public UserService(UserRepository repository, EmailService emailService) {
        this.repository = repository;
        this.emailService = emailService;
    }
    
    public void createUser(User user) {
        repository.save(user);
        emailService.sendWelcomeEmail(user.getEmail());
    }
}

// Использование
UserRepository repo = new UserRepository();
EmailService email = new EmailService();
UserService service = new UserService(repo, email);  // Инъекция!

Три способа инъекции зависимостей

1. Constructor Injection (Инъекция через конструктор)

Это лучший способ!

public class OrderService {
    private final PaymentProcessor paymentProcessor;
    private final InventoryService inventoryService;
    
    // Зависимости передаются через конструктор
    public OrderService(
        PaymentProcessor paymentProcessor,
        InventoryService inventoryService
    ) {
        this.paymentProcessor = paymentProcessor;
        this.inventoryService = inventoryService;
    }
    
    public void placeOrder(Order order) {
        inventoryService.reserve(order.getItems());
        paymentProcessor.charge(order.getTotal());
    }
}

// Использование
PaymentProcessor processor = new PaymentProcessor();
InventoryService inventory = new InventoryService();
OrderService service = new OrderService(processor, inventory);

Преимущества:

  • Зависимости видны в сигнатуре конструктора
  • Можно сделать поля final (неизменяемость)
  • Легко создавать тесты
  • Обязательные зависимости явно видны

2. Setter Injection (Инъекция через setter)

public class UserService {
    private UserRepository repository;  // Может быть null
    
    // Зависимость передаётся через метод
    public void setRepository(UserRepository repository) {
        this.repository = repository;
    }
    
    public void getUser(Long id) {
        if (repository == null) {
            throw new NullPointerException("Repository не инициализирован!");
        }
        return repository.findById(id);
    }
}

// Использование
UserService service = new UserService();
service.setRepository(new UserRepository());

Недостатки:

  • Зависимость может быть null
  • Сложнее тестировать
  • Порядок вызова методов важен

3. Interface Injection

// Интерфейс для инъекции
public interface RepositoryInjector {
    void injectRepository(UserRepository repository);
}

// Реализация
public class UserService implements RepositoryInjector {
    private UserRepository repository;
    
    @Override
    public void injectRepository(UserRepository repository) {
        this.repository = repository;
    }
}

// Используется редко, в основном в Spring

DI контейнеры

DI контейнер (DI Container) — это фреймворк, который автоматически управляет инъекцией зависимостей. Spring Framework — самый популярный DI контейнер в Java.

Spring Framework DI

// Интерфейсы
public interface UserRepository {
    User findById(Long id);
    void save(User user);
}

// Реализация
@Repository  // Spring автоматически создаст bean
public class JpaUserRepository implements UserRepository {
    @Autowired
    private JpaRepository jpaRepo;
    
    @Override
    public User findById(Long id) {
        return jpaRepo.findById(id).orElse(null);
    }
    
    @Override
    public void save(User user) {
        jpaRepo.save(user);
    }
}

// Сервис
@Service  // Spring автоматически создаст bean
public class UserService {
    private final UserRepository repository;
    private final EmailService emailService;
    
    // Spring автоматически инъектирует зависимости!
    @Autowired
    public UserService(
        UserRepository repository,
        EmailService emailService
    ) {
        this.repository = repository;
        this.emailService = emailService;
    }
    
    public void createUser(User user) {
        repository.save(user);
        emailService.sendWelcomeEmail(user.getEmail());
    }
}

// Контроллер
@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;
    
    @Autowired  // Spring инъектирует UserService
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        userService.createUser(user);
        return ResponseEntity.created(URI.create("/api/users/" + user.getId()))
                             .body(user);
    }
}

Практический пример: Email Service

// ❌ Без DI
public class OrderNotificationService {
    private GmailEmailService emailService = new GmailEmailService();  // Жёстко привязано!
    
    public void notifyOrderCreated(Order order) {
        emailService.send(
            order.getCustomer().getEmail(),
            "Order Confirmation",
            "Your order has been created"
        );
    }
}

// Проблема: если нужно использовать SendGridEmailService, нужно менять код!

// ✅ С DI
public interface EmailService {
    void send(String to, String subject, String body);
}

public class GmailEmailService implements EmailService {
    @Override
    public void send(String to, String subject, String body) {
        // Отправка через Gmail
    }
}

public class SendGridEmailService implements EmailService {
    @Override
    public void send(String to, String subject, String body) {
        // Отправка через SendGrid
    }
}

public class OrderNotificationService {
    private final EmailService emailService;  // Зависит от интерфейса!
    
    public OrderNotificationService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void notifyOrderCreated(Order order) {
        emailService.send(
            order.getCustomer().getEmail(),
            "Order Confirmation",
            "Your order has been created"
        );
    }
}

// Использование
OrderNotificationService service1 = new OrderNotificationService(
    new GmailEmailService()  // Легко переключаться!
);

OrderNotificationService service2 = new OrderNotificationService(
    new SendGridEmailService()  // Другая реализация, тот же код
);

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

// Mock для тестирования
public class MockEmailService implements EmailService {
    private List<String> sentEmails = new ArrayList<>();
    
    @Override
    public void send(String to, String subject, String body) {
        sentEmails.add(to);
    }
    
    public List<String> getSentEmails() {
        return sentEmails;
    }
}

// Тест
@Test
public void testOrderNotification() {
    MockEmailService mockEmail = new MockEmailService();
    OrderNotificationService service = new OrderNotificationService(mockEmail);
    
    Order order = new Order();
    order.getCustomer().setEmail("customer@example.com");
    
    service.notifyOrderCreated(order);
    
    // Проверяем, что email был отправлен
    assertThat(mockEmail.getSentEmails())
        .contains("customer@example.com");
}

Преимущества DI

  1. Слабая связанность — код не зависит от конкретных реализаций
  2. Тестируемость — легко подставить mock объекты
  3. Гибкость — легко менять реализации без изменения кода
  4. Переиспользуемость — компоненты легко комбинируются по-новому
  5. Явность — зависимости видны в сигнатуре конструктора

Разница: Inversion of Control (IoC)

IoC — более общий паттерн, означающий что фреймворк контролирует поток выполнения. DI — конкретная реализация IoC, где зависимости инъектируются.

IoC (Inversion of Control)
├── Dependency Injection (DI)
├── Factory Pattern
├── Service Locator
└── Другие паттерны

Заключение

Дependency Injection — это мощный паттерн, который делает код гибким, тестируемым и maintainable. Spring Framework делает DI простым и удобным. Всегда передавай зависимости через конструктор вместо создания их внутри класса.