← Назад к вопросам
Что такое 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);
}
}
Выводы
- DIP — зависи от абстракций, не от конкретных классов
- Используй интерфейсы для определения контрактов
- Внедряй зависимости через:
- Constructor Injection (лучший вариант)
- Setter Injection
- Interface Injection
- Преимущества:
- Слабая связанность (loose coupling)
- Легче тестировать
- Легче менять реализацию
- Гибкая архитектура
- Spring Framework полностью основан на DIP через @Autowired и конструктор инъекцию