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

Что такое Dependency Inversion?

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

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

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

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

Dependency Inversion Principle (DIP)

Определение

Dependency Inversion — это один из пяти принципов SOLID. Он гласит:

"Высокоуровневые модули не должны зависеть от низкоуровневых модулей. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей, детали должны зависеть от абстракций."

Просто: зависи от интерфейсов, а не от конкретных реализаций.

Пример проблемы (нарушение DIP)

// ПЛОХО: прямая зависимость от конкретного класса
public class OrderService {
    private PostgresqlDatabase database;  // Зависимость от конкретного класса!
    
    public OrderService() {
        this.database = new PostgresqlDatabase();  // Тесно связано
    }
    
    public void saveOrder(Order order) {
        database.insert("orders", order);  // PostgreSQL специфичный код
    }
}

// Проблемы:
// 1. OrderService сильно привязана к PostgreSQL
// 2. Нельзя тестировать с mock базой
// 3. Нельзя переключиться на другую БД без переписания OrderService
// 4. OrderService зависит от низкоуровневого модуля (БД)

public class Main {
    public static void main(String[] args) {
        // Если нужна другая БД, нужно переписывать OrderService
        OrderService service = new OrderService();
        service.saveOrder(new Order("123"));
    }
}

Решение: инверсия зависимости

// 1. Определяем абстракцию (интерфейс)
public interface Database {
    void insert(String table, Object data);
    Object select(String table, String id);
    void update(String table, Object data);
    void delete(String table, String id);
}

// 2. Реализации зависят от интерфейса
public class PostgresqlDatabase implements Database {
    @Override
    public void insert(String table, Object data) {
        System.out.println("PostgreSQL INSERT: " + data);
    }
    
    @Override
    public Object select(String table, String id) {
        System.out.println("PostgreSQL SELECT: " + id);
        return null;
    }
    
    @Override
    public void update(String table, Object data) {
        System.out.println("PostgreSQL UPDATE: " + data);
    }
    
    @Override
    public void delete(String table, String id) {
        System.out.println("PostgreSQL DELETE: " + id);
    }
}

public class MongoDatabase implements Database {
    @Override
    public void insert(String table, Object data) {
        System.out.println("MongoDB INSERT: " + data);
    }
    
    @Override
    public Object select(String table, String id) {
        System.out.println("MongoDB SELECT: " + id);
        return null;
    }
    
    @Override
    public void update(String table, Object data) {
        System.out.println("MongoDB UPDATE: " + data);
    }
    
    @Override
    public void delete(String table, String id) {
        System.out.println("MongoDB DELETE: " + id);
    }
}

// 3. OrderService зависит от ИНТЕРФЕЙСА, не от реализации
public class OrderService {
    private Database database;  // Зависимость от абстракции!
    
    // Dependency Injection через конструктор
    public OrderService(Database database) {
        this.database = database;  // Внедряем зависимость
    }
    
    public void saveOrder(Order order) {
        database.insert("orders", order);  // Работает с любой БД!
    }
    
    public Order getOrder(String id) {
        return (Order) database.select("orders", id);
    }
    
    public void updateOrder(Order order) {
        database.update("orders", order);
    }
    
    public void deleteOrder(String id) {
        database.delete("orders", id);
    }
}

// 4. Использование
public class Main {
    public static void main(String[] args) {
        // С PostgreSQL
        Database postgresDb = new PostgresqlDatabase();
        OrderService service1 = new OrderService(postgresDb);
        service1.saveOrder(new Order("123"));
        
        // С MongoDB
        Database mongoDb = new MongoDatabase();
        OrderService service2 = new OrderService(mongoDb);
        service2.saveOrder(new Order("456"));
        
        // С mock базой для тестов
        Database mockDb = new MockDatabase();
        OrderService service3 = new OrderService(mockDb);
        service3.saveOrder(new Order("789"));
    }
}

Стрелки зависимостей

БЕЗ DIP (нарушение):
┌─────────────┐
│ OrderService│──→ PostgresqlDatabase
└─────────────┘    (конкретный класс)

Особенность: OrderService зависит от низкоуровневого модуля (БД)


С DIP (правильно):
┌─────────────┐
│ OrderService│──→ Database (интерфейс)
└─────────────┘     ↑
                    │
                ┌───┴──────┬─────────┐
                │          │         │
           PostgreSQL   MongoDB   InMemory
           (деталь)     (деталь)  (деталь)

Особенность: оба модуля зависят от абстракции (интерфейса)

Сравнение подходов

// ❌ БЕЗ DIP: Tight Coupling
public class ReportGenerator {
    private ExcelExporter exporter;  // Конкретный класс
    
    public ReportGenerator() {
        this.exporter = new ExcelExporter();  // Hard to test
    }
    
    public void generateReport(List<Data> data) {
        exporter.export(data);  // Зависит от Excel
    }
}

// ✅ С DIP: Loose Coupling
public interface Exporter {
    void export(List<Data> data);
}

public class ExcelExporter implements Exporter {
    @Override
    public void export(List<Data> data) {
        System.out.println("Exporting to Excel");
    }
}

public class CsvExporter implements Exporter {
    @Override
    public void export(List<Data> data) {
        System.out.println("Exporting to CSV");
    }
}

public class ReportGenerator {
    private Exporter exporter;  // Интерфейс!
    
    public ReportGenerator(Exporter exporter) {
        this.exporter = exporter;  // Гибко
    }
    
    public void generateReport(List<Data> data) {
        exporter.export(data);  // Работает с любым exporter
    }
}

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

// Mock для тестирования
public class MockDatabase implements Database {
    private List<Object> data = new ArrayList<>();
    
    @Override
    public void insert(String table, Object data) {
        this.data.add(data);
    }
    
    @Override
    public Object select(String table, String id) {
        return this.data.isEmpty() ? null : this.data.get(0);
    }
    
    @Override
    public void update(String table, Object data) {
        // Mock implementation
    }
    
    @Override
    public void delete(String table, String id) {
        this.data.clear();
    }
}

// Тест
public class OrderServiceTest {
    @Test
    public void testSaveOrder() {
        // Используем mock вместо реальной БД
        Database mockDb = new MockDatabase();
        OrderService service = new OrderService(mockDb);
        
        Order order = new Order("123");
        service.saveOrder(order);
        
        // Проверяем результат
        Order retrieved = service.getOrder("123");
        assertNotNull(retrieved);
    }
}

Spring и DIP

Spring Framework полностью построен на DIP через Dependency Injection:

@Configuration
public class AppConfig {
    @Bean
    public Database database() {
        // В dev можно вернуть PostgreSQL
        // В test можно вернуть MockDatabase
        return new PostgresqlDatabase();
    }
    
    @Bean
    public OrderService orderService(Database database) {
        // Spring внедрит зависимость
        return new OrderService(database);
    }
}

// Использование
@Service
public class OrderController {
    private OrderService orderService;
    
    @Autowired
    public OrderController(OrderService orderService) {
        this.orderService = orderService;  // DI от Spring
    }
}

Реальный пример: Email сервис

// Абстракция
public interface EmailService {
    void sendEmail(String to, String subject, String body);
}

// Реализации
public class GmailService implements EmailService {
    @Override
    public void sendEmail(String to, String subject, String body) {
        // Отправка через Gmail API
    }
}

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

public class MockEmailService implements EmailService {
    @Override
    public void sendEmail(String to, String subject, String body) {
        System.out.println("Mock: Email sent to " + to);
    }
}

// Высокоуровневый модуль
public class UserRegistrationService {
    private EmailService emailService;
    
    public UserRegistrationService(EmailService emailService) {
        this.emailService = emailService;
    }
    
    public void registerUser(String email, String name) {
        // Создаём пользователя
        User user = new User(email, name);
        
        // Отправляем письмо (не знаем как отправляется!)
        emailService.sendEmail(
            email,
            "Welcome",
            "Welcome to our platform, " + name
        );
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        // Production
        EmailService emailService = new SendGridService();
        UserRegistrationService service = new UserRegistrationService(emailService);
        
        // Tests
        EmailService mockEmail = new MockEmailService();
        UserRegistrationService testService = new UserRegistrationService(mockEmail);
    }
}

Выводы

  1. DIP — зависи от абстракций, не от конкретных классов
  2. Используй интерфейсы для определения контрактов
  3. Внедряй зависимости через:
    • Constructor Injection (лучший вариант)
    • Setter Injection
    • Interface Injection
  4. Преимущества:
    • Слабая связанность (loose coupling)
    • Легче тестировать
    • Легче менять реализацию
    • Гибкая архитектура
  5. Spring Framework полностью основан на DIP через @Autowired и конструктор инъекцию
Что такое Dependency Inversion? | PrepBro