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

Приведи пример инверсии зависимостей

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

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

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

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

Инверсия зависимостей (Dependency Inversion): Практический пример

Инверсия зависимостей (Dependency Inversion Principle, DIP) — это один из ключевых принципов SOLID. Он гласит: высокоуровневые модули не должны зависеть от низкоуровневых; оба должны зависеть от абстракций.

Проблема: Прямые зависимости

Представь систему отправки уведомлений. Наивный подход:

// ❌ ПЛОХО: EmailService жёстко привязана
public class OrderService {
    private EmailService emailService = new EmailService();
    
    public void createOrder(Order order) {
        // Создаём заказ
        saveOrder(order);
        
        // Отправляем письмо
        emailService.send(
            order.getCustomerEmail(),
            "Order #" + order.getId() + " created"
        );
    }
    
    private void saveOrder(Order order) { ... }
}

public class EmailService {
    public void send(String email, String message) {
        // Отправляем через SMTP
        System.out.println("Sending to " + email + ": " + message);
    }
}

Проблемы:

  • OrderService жёстко зависит от EmailService (тесная связь)
  • Если нужна SMS вместо Email — меняешь OrderService
  • Тестирование сложное — нужно мокировать реальную отправку
  • Нельзя легко добавить другой способ уведомления

Решение 1: Зависимость от интерфейса

Введём абстракцию (интерфейс), которая описывает контракт для уведомлений:

// Абстракция: интерфейс
public interface NotificationService {
    void notify(String recipient, String message);
}

// Конкретные реализации
public class EmailService implements NotificationService {
    @Override
    public void notify(String recipient, String message) {
        // Отправляем через SMTP
        System.out.println("Email to " + recipient + ": " + message);
    }
}

public class SmsService implements NotificationService {
    @Override
    public void notify(String recipient, String message) {
        // Отправляем через SMS шлюз
        System.out.println("SMS to " + recipient + ": " + message);
    }
}

public class SlackService implements NotificationService {
    @Override
    public void notify(String recipient, String message) {
        // Отправляем через Slack API
        System.out.println("Slack to " + recipient + ": " + message);
    }
}

// ✅ ХОРОШО: OrderService зависит от абстракции, не от реализации
public class OrderService {
    private NotificationService notificationService;
    
    // Инъекция зависимости через конструктор
    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
    
    public void createOrder(Order order) {
        // Создаём заказ
        saveOrder(order);
        
        // Отправляем уведомление через любой сервис
        notificationService.notify(
            order.getCustomerEmail(),
            "Order #" + order.getId() + " created"
        );
    }
    
    private void saveOrder(Order order) { ... }
}

Преимущества инверсии зависимостей

1. Слабая связанность (Loose Coupling)

// Клиент может использовать любую реализацию
OrderService service1 = new OrderService(new EmailService());
OrderService service2 = new OrderService(new SmsService());
OrderService service3 = new OrderService(new SlackService());

// OrderService не знает и не волнует, какая реализация используется

2. Простота тестирования

@Test
public void testOrderCreation() {
    // Создаём mock
    NotificationService mockNotifier = mock(NotificationService.class);
    
    // Инъектим mock в OrderService
    OrderService service = new OrderService(mockNotifier);
    
    // Тестируем
    service.createOrder(new Order());
    
    // Проверяем, что notifier был вызван
    verify(mockNotifier).notify(any(), any());
}

3. Легко добавлять новые реализации

// Нужен PushNotificationService? Просто добавляем новый класс
public class PushNotificationService implements NotificationService {
    @Override
    public void notify(String recipient, String message) {
        // Firebase Cloud Messaging
    }
}

// OrderService не меняется вообще!
NotificationService push = new PushNotificationService();
OrderService service = new OrderService(push);

4. Стратегия: выбор реализации в runtime

public class NotificationFactory {
    public static NotificationService create(String type) {
        return switch (type) {
            case "email" -> new EmailService();
            case "sms" -> new SmsService();
            case "slack" -> new SlackService();
            case "push" -> new PushNotificationService();
            default -> throw new IllegalArgumentException("Unknown type");
        };
    }
}

// Использование
String notificationType = configService.getNotificationType();  // из конфига
NotificationService notifier = NotificationFactory.create(notificationType);
OrderService service = new OrderService(notifier);

Инверсия зависимостей в Spring

Spring автоматизирует инъекцию зависимостей через IoC контейнер:

// Бины регистрируются в контексте
@Configuration
public class NotificationConfig {
    @Bean
    public NotificationService notificationService() {
        // Выбираем реализацию в зависимости от конфига
        String type = System.getenv("NOTIFICATION_TYPE");
        return switch (type) {
            case "email" -> new EmailService();
            case "sms" -> new SmsService();
            default -> new EmailService();
        };
    }
}

// Spring автоматически инъектит зависимость
@Service
public class OrderService {
    private final NotificationService notificationService;
    
    @Autowired  // Spring находит NotificationService в контексте
    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
    
    public void createOrder(Order order) {
        saveOrder(order);
        notificationService.notify(
            order.getCustomerEmail(),
            "Order created"
        );
    }
}

Продвинутый пример: Multi-channel уведомления

// Интерфейс для отправки в несколько каналов
public interface NotificationService {
    void notify(Notification notification);
}

public class Notification {
    private String recipient;
    private String message;
    private Set<Channel> channels;  // email, sms, push, slack
    
    // Getters...
}

// Реализация: отправляет в выбранные каналы
@Service
public class MultiChannelNotificationService implements NotificationService {
    private final Map<Channel, NotificationChannel> channels;
    
    public MultiChannelNotificationService(
            EmailService emailService,
            SmsService smsService,
            SlackService slackService) {
        channels = Map.of(
            Channel.EMAIL, emailService,
            Channel.SMS, smsService,
            Channel.SLACK, slackService
        );
    }
    
    @Override
    public void notify(Notification notification) {
        // Отправляем через каждый выбранный канал
        notification.getChannels().forEach(channel -> {
            NotificationChannel service = channels.get(channel);
            if (service != null) {
                service.notify(notification.getRecipient(), notification.getMessage());
            }
        });
    }
}

public interface NotificationChannel {
    void notify(String recipient, String message);
}

// OrderService использует MultiChannelNotificationService
@Service
public class OrderService {
    private final NotificationService notificationService;
    
    @Autowired
    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
    
    public void createOrder(Order order) {
        saveOrder(order);
        
        Notification notification = new Notification();
        notification.setRecipient(order.getCustomerEmail());
        notification.setMessage("Order #" + order.getId() + " created");
        notification.setChannels(Set.of(Channel.EMAIL, Channel.SLACK));
        
        notificationService.notify(notification);
    }
}

Принцип инверсии в действии

БЕЗ DIP (плохо):
OrderService → EmailService → SMTP
OrderService → SmsService → Twilio
OrderService → SlackService → Slack API
(OrderService зависит от конкретных реализаций)

С DIP (хорошо):
OrderService ↓
         ↓
   NotificationService (интерфейс)
    ↙       ↓       ↘
Email   SMS   Slack   Push
(OrderService зависит от абстракции, которая имеет множество реализаций)

Когда применять инверсию зависимостей

Обязательно используй, когда:

  • Есть несколько реализаций одной функции
  • Нужна лёгкость тестирования
  • Нужна гибкость и расширяемость
  • Используешь паттерны Strategy, Decorator, Factory

Можно не использовать (но лучше всё равно):

  • Простые классы без вариативности
  • Утилиты и helpers без состояния

Заключение

Инверсия зависимостей — это не просто паттерн, это философия разработки, которая делает код:

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

Spring Boot делает инверсию зависимостей естественной и простой через IoC контейнер.

Приведи пример инверсии зависимостей | PrepBro