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

Приведи пример использования Dependency Inversion Principle

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

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

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

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

Dependency Inversion Principle (DIP) — практический пример

DIP — это пятый принцип SOLID. Он гласит: зависи от абстракций (интерфейсов), а не от конкретных реализаций.

Суть принципа

❌ НЕПРАВИЛЬНО (зависимость от конкретной реализации)

high-level module (PaymentService)
         ↓
    зависит от
         ↓
low-level module (CreditCardPayment)

Проблема: если менять CreditCardPayment, меняется PaymentService!


✓ ПРАВИЛЬНО (зависимость через интерфейс)

high-level module (PaymentService)
         ↓
    зависит от
         ↓
     Abstraction (PaymentProcessor интерфейс)
         ↑
    реализует
         ↑
low-level module (CreditCardPayment, PayPalPayment)

Преимущество: PaymentService не знает о деталях реализации!

Пример 1: ❌ БЕЗ Dependency Inversion

// Низкоуровневый модуль (конкретная реализация)
public class CreditCardPayment {
    public void processPayment(BigDecimal amount) {
        System.out.println("Processing credit card payment: " + amount);
        // Логика обработки платежа через кредитную карту
        validateCard();
        chargeCard(amount);
    }
    
    private void validateCard() { }
    private void chargeCard(BigDecimal amount) { }
}

// Высокоуровневый модуль (зависит от конкретной реализации) - ПЛОХО!
public class PaymentService {
    private CreditCardPayment creditCardPayment;  // Зависит от КОНКРЕТНОГО класса!
    
    public PaymentService() {
        this.creditCardPayment = new CreditCardPayment();  // Создаёт сам
    }
    
    public void processOrder(Order order) {
        // Может обрабатывать ТОЛЬКО кредитные карты
        creditCardPayment.processPayment(order.getTotal());
    }
}

// Проблемы:
// 1. PaymentService жёстко связана с CreditCardPayment
// 2. Невозможно добавить PayPal без изменения PaymentService
// 3. Сложно тестировать (нельзя замокировать)
// 4. Нарушение Dependency Inversion Principle

Тестирование без DIP очень сложно:

@Test
public void testProcessOrder() {
    PaymentService service = new PaymentService();
    Order order = new Order(new BigDecimal("100"));
    
    // Проблема: service.processOrder() вызывает РЕАЛЬНОЕ начисление!
    // Нельзя замокировать CreditCardPayment
    // Тест выполняет реальный платёж
    service.processOrder(order);  // ❌ Опасно!
}

Пример 2: ✓ С Dependency Inversion

// АБСТРАКЦИЯ (интерфейс) - определяет контракт
public interface PaymentProcessor {
    void processPayment(BigDecimal amount);
    void refund(BigDecimal amount);
}

// Конкретная реализация 1
public class CreditCardPayment implements PaymentProcessor {
    @Override
    public void processPayment(BigDecimal amount) {
        System.out.println("Processing credit card payment: " + amount);
        validateCard();
        chargeCard(amount);
    }
    
    @Override
    public void refund(BigDecimal amount) {
        System.out.println("Refunding credit card: " + amount);
    }
    
    private void validateCard() { }
    private void chargeCard(BigDecimal amount) { }
}

// Конкретная реализация 2
public class PayPalPayment implements PaymentProcessor {
    @Override
    public void processPayment(BigDecimal amount) {
        System.out.println("Processing PayPal payment: " + amount);
        authenticatePayPal();
        chargePayPal(amount);
    }
    
    @Override
    public void refund(BigDecimal amount) {
        System.out.println("Refunding PayPal: " + amount);
    }
    
    private void authenticatePayPal() { }
    private void chargePayPal(BigDecimal amount) { }
}

// Конкретная реализация 3
public class BitcoinPayment implements PaymentProcessor {
    @Override
    public void processPayment(BigDecimal amount) {
        System.out.println("Processing Bitcoin payment: " + amount);
        validateWallet();
        transferBitcoin(amount);
    }
    
    @Override
    public void refund(BigDecimal amount) {
        System.out.println("Bitcoin refund not supported");
    }
    
    private void validateWallet() { }
    private void transferBitcoin(BigDecimal amount) { }
}

// ВЫСОКОУРОВНЕВЫЙ модуль (зависит от АБСТРАКЦИИ) - ПРАВИЛЬНО!
public class PaymentService {
    private PaymentProcessor paymentProcessor;  // Зависит от интерфейса!
    
    // Dependency Injection через конструктор
    public PaymentService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;  // Инъекция зависимости
    }
    
    public void processOrder(Order order) {
        // Может работать с ЛЮБОЙ реализацией PaymentProcessor
        paymentProcessor.processPayment(order.getTotal());
    }
    
    public void refundOrder(Order order) {
        paymentProcessor.refund(order.getTotal());
    }
}

// Использование
public class OrderApplication {
    public static void main(String[] args) {
        Order order = new Order(new BigDecimal("99.99"));
        
        // Пример 1: Платёж через кредитную карту
        PaymentProcessor creditCard = new CreditCardPayment();
        PaymentService service1 = new PaymentService(creditCard);
        service1.processOrder(order);  // Платёж через кредитную карту
        
        // Пример 2: Платёж через PayPal (БЕЗ изменения PaymentService!)
        PaymentProcessor paypal = new PayPalPayment();
        PaymentService service2 = new PaymentService(paypal);
        service2.processOrder(order);  // Платёж через PayPal
        
        // Пример 3: Платёж через Bitcoin
        PaymentProcessor bitcoin = new BitcoinPayment();
        PaymentService service3 = new PaymentService(bitcoin);
        service3.processOrder(order);  // Платёж через Bitcoin
    }
}

// Вывод:
// Processing credit card payment: 99.99
// Processing PayPal payment: 99.99
// Processing Bitcoin payment: 99.99

// PaymentService осталась НЕИЗМЕННОЙ!
// Добавили новые платёжные системы БЕЗ изменения PaymentService!

Тестирование с DIP очень простое:

public class PaymentServiceTest {
    
    @Test
    public void testProcessOrderWithMockProcessor() {
        // Создаём мок вместо реальной реализации
        PaymentProcessor mockProcessor = mock(PaymentProcessor.class);
        
        PaymentService service = new PaymentService(mockProcessor);
        Order order = new Order(new BigDecimal("100"));
        
        service.processOrder(order);
        
        // Проверяем, что мок был вызван с правильными параметрами
        verify(mockProcessor).processPayment(new BigDecimal("100"));
        // ✓ Нет реального платежа!
        // ✓ Легко тестировать!
    }
    
    @Test
    public void testProcessOrderWithFakeProcessor() {
        // Или используем Fake реализацию для тестов
        PaymentProcessor fakeProcessor = new FakePaymentProcessor();
        
        PaymentService service = new PaymentService(fakeProcessor);
        Order order = new Order(new BigDecimal("50"));
        
        service.processOrder(order);
        
        // Проверяем результаты
        assertTrue(fakeProcessor.isProcessed());
    }
}

public class FakePaymentProcessor implements PaymentProcessor {
    private boolean processed = false;
    
    @Override
    public void processPayment(BigDecimal amount) {
        processed = true;  // Просто флаг, без реального платежа
    }
    
    @Override
    public void refund(BigDecimal amount) { }
    
    public boolean isProcessed() {
        return processed;
    }
}

Пример 3: Spring и Dependency Inversion

// Spring контейнер автоматически создаёт и инъектит зависимости

@Service
public class PaymentService {
    private final PaymentProcessor paymentProcessor;
    
    // Spring автоматически выберет реализацию
    @Autowired
    public PaymentService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    
    public void processOrder(Order order) {
        paymentProcessor.processPayment(order.getTotal());
    }
}

// Конфигурация выбирает нужную реализацию
@Configuration
public class PaymentConfig {
    
    @Bean
    @ConditionalOnProperty(name = "payment.method", havingValue = "creditcard")
    public PaymentProcessor creditCardPayment() {
        return new CreditCardPayment();
    }
    
    @Bean
    @ConditionalOnProperty(name = "payment.method", havingValue = "paypal")
    public PaymentProcessor paypalPayment() {
        return new PayPalPayment();
    }
}

// application.properties
// payment.method=paypal

// Результат: Spring инъектит PayPalPayment в PaymentService!

DIP в многоуровневой архитектуре

// Domain Layer (бизнес-логика) - зависит от интерфейсов
public interface UserRepository {
    User save(User user);
    User findById(Long id);
}

public class UserService {
    private final UserRepository userRepository;  // Зависит от интерфейса!
    
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public void createUser(String name) {
        User user = new User(name);
        userRepository.save(user);
    }
}

// Infrastructure Layer - конкретные реализации
@Repository
public class JpaUserRepository implements UserRepository {
    @Override
    public User save(User user) {
        // Сохранение через JPA
    }
    
    @Override
    public User findById(Long id) {
        // Поиск через JPA
    }
}

// Если нужно переходить на другую БД:
@Repository
public class MongoUserRepository implements UserRepository {
    @Override
    public User save(User user) {
        // Сохранение через MongoDB
    }
    
    @Override
    public User findById(Long id) {
        // Поиск через MongoDB
    }
}

// UserService не меняется, работает с обеими реализациями!

Плюсы Dependency Inversion Principle

Слабая связанность — высокоуровневые модули независимы от низкоуровневых ✓ Гибкость — легко менять реализации ✓ Тестируемость — легко создавать моки ✓ Расширяемость — добавлять новые реализации без изменения существующего кода ✓ Чистая архитектура — зависимости направлены в сторону абстракций

Минусы

❌ Больше кода (интерфейсы, реализации) ❌ Может быть над-инжиниринг для простых проектов ❌ Требует хорошего понимания архитектуры

Вывод

Dependency Inversion Principle — это о том, что зависимости должны указывать на абстракции, а не на конкретные реализации. Это достигается через:

  1. Интерфейсы — определение контрактов
  2. Dependency Injection — передача зависимостей извне
  3. Полиморфизм — использование общего интерфейса

Это делает систему гибкой, тестируемой и легко расширяемой!