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

Что такое принцип DI?

1.0 Junior🔥 221 комментариев
#SOLID и паттерны проектирования

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

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

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

Что такое принцип DI (Dependency Injection)?

Dependency Injection (DI) — это принцип проектирования, который гласит: объект не должен сам создавать свои зависимости, а должен получать их из внешнего источника. Это фундаментальный принцип чистой архитектуры и SOLID.

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

Вместо того чтобы класс самостоятельно создавал нужные ему объекты, эти объекты передаются в него. Это делает код более гибким, тестируемым и слабо связанным.

Пример без DI (плохо)

public class PaymentService {
    private DatabaseConnection dbConnection;  // Жёсткая зависимость
    private EmailService emailService;        // Жёсткая зависимость
    
    public PaymentService() {
        // Класс сам создаёт свои зависимости
        this.dbConnection = new DatabaseConnection("localhost", 5432);
        this.emailService = new EmailService("smtp.gmail.com");
    }
    
    public void processPayment(Payment payment) {
        dbConnection.save(payment);  // Использует конкретную реализацию
        emailService.sendReceipt(payment.getEmail());  // Использует конкретную реализацию
    }
}

// Проблемы:
// 1. Сложно тестировать — нельзя подменить DatabaseConnection на mock
// 2. Тесно связано — изменение конструктора DatabaseConnection ломает код
// 3. Жёсткая архитектура — нельзя использовать разные реализации
public class PaymentServiceTest {
    @Test
    public void testPayment() {
        PaymentService service = new PaymentService();
        // Но этот тест создаст РЕАЛЬНУЮ БД соединение, не mock!
        // Это медленно и опасно
    }
}

Пример с DI (хорошо)

// 1. Определяем интерфейсы (абстракции)
public interface Database {
    void save(Payment payment);
}

public interface Mailer {
    void send(String email, String message);
}

// 2. Классы реализуют интерфейсы (конкретные реализации)
public class PostgresDatabase implements Database {
    @Override
    public void save(Payment payment) {
        // Сохраняем в PostgreSQL
    }
}

public class GmailMailer implements Mailer {
    @Override
    public void send(String email, String message) {
        // Отправляем через Gmail
    }
}

// 3. PaymentService получает зависимости (не создаёт их)
public class PaymentService {
    private final Database database;
    private final Mailer mailer;
    
    // Зависимости ИНЪЕКТИРУЮТСЯ через конструктор
    public PaymentService(Database database, Mailer mailer) {
        this.database = database;
        this.mailer = mailer;
    }
    
    public void processPayment(Payment payment) {
        database.save(payment);
        mailer.send(payment.getEmail(), "Receipt");
    }
}

// 4. Тестирование становится просто — используем mocks
public class PaymentServiceTest {
    @Test
    public void testPayment() {
        // Создаём mock-объекты
        Database mockDb = mock(Database.class);
        Mailer mockMailer = mock(Mailer.class);
        
        PaymentService service = new PaymentService(mockDb, mockMailer);
        Payment payment = new Payment(100, "user@example.com");
        
        service.processPayment(payment);
        
        // Проверяем, что методы были вызваны с правильными параметрами
        verify(mockDb).save(payment);
        verify(mockMailer).send("user@example.com", "Receipt");
    }
}

Способы инъекции

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

public class UserRepository {
    private final DatabaseConnection db;
    
    // Constructor Injection
    public UserRepository(DatabaseConnection db) {
        this.db = db;
    }
}

// Использование
DatabaseConnection dbConnection = new DatabaseConnection();
UserRepository repo = new UserRepository(dbConnection);

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

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

2. Инъекция через setter

public class UserService {
    private DatabaseConnection db;
    
    // Setter Injection
    public void setDatabase(DatabaseConnection db) {
        this.db = db;
    }
}

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

Недостатки:

  • Объект может быть создан без зависимостей (нарушение инвариантов)
  • Сложнее с многопоточностью
  • Не видно требуемых зависимостей

3. Инъекция через интерфейс

public interface DatabaseInjectable {
    void setDatabase(DatabaseConnection db);
}

public class UserService implements DatabaseInjectable {
    private DatabaseConnection db;
    
    @Override
    public void setDatabase(DatabaseConnection db) {
        this.db = db;
    }
}

Spring Framework — реализация DI

// Spring автоматически управляет зависимостями

// 1. Определяем бины (управляемые объекты)
@Configuration
public class AppConfig {
    @Bean
    public DatabaseConnection databaseConnection() {
        return new PostgresDatabase("localhost", 5432);
    }
    
    @Bean
    public Mailer mailer() {
        return new GmailMailer("app@example.com");
    }
}

// 2. Spring инъектирует зависимости автоматически
@Service
public class PaymentService {
    private final Database database;
    private final Mailer mailer;
    
    @Autowired  // Spring найдёт бины и инъектирует
    public PaymentService(Database database, Mailer mailer) {
        this.database = database;
        this.mailer = mailer;
    }
}

// 3. Или через аннотацию на поле
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
    
    public void createOrder(Order order) {
        paymentService.processPayment(order.getPayment());
    }
}

DI контейнер

DI контейнер — это объект, который:

  1. Знает, какие классы нужно создавать
  2. Знает, какие зависимости каждому классу нужны
  3. Создаёт объекты в правильном порядке
  4. Инъектирует зависимости
// Упрощённая реализация DI контейнера
public class DIContainer {
    private Map<Class<?>, Class<?>> bindings = new HashMap<>();
    
    public <T> void bind(Class<T> interface_, Class<?> implementation) {
        bindings.put(interface_, implementation);
    }
    
    public <T> T get(Class<T> type) {
        Class<?> impl = bindings.getOrDefault(type, type);
        
        // Ищем конструктор с зависимостями
        Constructor<?>[] constructors = impl.getDeclaredConstructors();
        if (constructors.length == 0) {
            throw new RuntimeException("No constructors found");
        }
        
        Constructor<?> constructor = constructors[0];
        Class<?>[] paramTypes = constructor.getParameterTypes();
        Object[] params = new Object[paramTypes.length];
        
        // Рекурсивно получаем зависимости
        for (int i = 0; i < paramTypes.length; i++) {
            params[i] = get(paramTypes[i]);
        }
        
        try {
            return (T) constructor.newInstance(params);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

// Использование
DIContainer container = new DIContainer();
container.bind(Database.class, PostgresDatabase.class);
container.bind(Mailer.class, GmailMailer.class);

PaymentService service = container.get(PaymentService.class);

Принципы DI

  1. Инвертирование управления (IoC) — контроль создания объектов передаётся контейнеру
  2. Слабая связанность — классы зависят от абстракций, не от конкретных реализаций
  3. Гибкость — легко подменить реализацию
  4. Тестируемость — можно использовать mocks
  5. Конфигурируемость — легко менять поведение без изменения кода

Резюме

Dependency Injection — это не просто техника, это архитектурный принцип:

  • Объекты не создают свои зависимости
  • Зависимости передаются через конструктор (предпочтительно)
  • Зависит от абстракций, не от конкретизаций
  • Делает код более тестируемым, гибким и поддерживаемым
  • Spring Framework полностью реализует этот принцип

ДI — это фундамент чистой архитектуры и SOLID принципов в Java.

Что такое принцип DI? | PrepBro