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

Что значит D в SOLID?

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

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

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

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

# D в SOLID: Dependency Inversion Principle

D в SOLID расшифровывается как Dependency Inversion Principle (DIP) — принцип инверсии зависимостей.

Определение

Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Проблема: нарушение DIP

// ПЛОХО: высокоуровневый класс зависит от низкоуровневого
public class PaymentService {
    private PayPalPaymentProvider provider; // Жёсткая зависимость
    
    public void processPayment(BigDecimal amount) {
        provider.charge(amount); // Тесно связано с PayPal
    }
}

public class PayPalPaymentProvider {
    public void charge(BigDecimal amount) {
        // Коммуникация с PayPal API
    }
}

Проблемы:

  • Если нужно сменить PayPal на Stripe, придётся переписывать PaymentService
  • Невозможно тестировать PaymentService без реального PayPal API
  • PaymentService знает внутренние детали PayPalPaymentProvider
  • Высокий coupling, низкий cohesion

Решение: применение DIP

// ХОРОШО: зависимость от абстракции

// 1. Определяем абстракцию (интерфейс)
public interface PaymentProvider {
    void charge(BigDecimal amount);
}

// 2. Реализации зависят от интерфейса
public class PayPalPaymentProvider implements PaymentProvider {
    @Override
    public void charge(BigDecimal amount) {
        // PayPal implementation
    }
}

public class StripePaymentProvider implements PaymentProvider {
    @Override
    public void charge(BigDecimal amount) {
        // Stripe implementation
    }
}

// 3. Высокоуровневый сервис зависит от абстракции
public class PaymentService {
    private final PaymentProvider provider; // Зависит от интерфейса, не от реализации
    
    // Инъекция через конструктор
    public PaymentService(PaymentProvider provider) {
        this.provider = provider;
    }
    
    public void processPayment(BigDecimal amount) {
        provider.charge(amount);
    }
}

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

  • Можно легко заменить PayPalPaymentProvider на StripePaymentProvider
  • Просто тестировать с Mock объектом
  • Слабая связанность, можно менять детали без влияния на PaymentService

Инъекция зависимостей (Dependency Injection)

DIP реализуется через инъекцию зависимостей:

1. Constructor Injection (рекомендуется)

@Service
public class PaymentService {
    private final PaymentProvider provider;
    private final NotificationService notificationService;
    
    // Spring автоматически инъектирует зависимости
    @Autowired
    public PaymentService(PaymentProvider provider, NotificationService notificationService) {
        this.provider = provider;
        this.notificationService = notificationService;
    }
}

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

  • Все зависимости явно видны
  • Легко сделать final fields (immutability)
  • Легко тестировать
  • Spring проверяет зависимости при инициализации

2. Setter Injection

@Service
public class PaymentService {
    private PaymentProvider provider;
    
    @Autowired
    public void setProvider(PaymentProvider provider) {
        this.provider = provider;
    }
}

Проблемы:

  • Зависимости не очевидны
  • Можно создать объект с null зависимостями
  • Сложнее тестировать

3. Field Injection

@Service
public class PaymentService {
    @Autowired
    private PaymentProvider provider; // Не рекомендуется
}

Проблемы:

  • Невозможно сделать final
  • Трудно тестировать
  • Зависимости скрыты

Практический пример с Spring

// 1. Интерфейс
public interface UserRepository {
    User findById(Long id);
    void save(User user);
}

// 2. Реализация
@Repository
public class JpaUserRepository implements UserRepository {
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public User findById(Long id) {
        return entityManager.find(User.class, id);
    }
    
    @Override
    public void save(User user) {
        entityManager.persist(user);
    }
}

// 3. Бизнес-логика зависит от интерфейса
@Service
public class UserService {
    private final UserRepository repository;
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public void updateUserEmail(Long userId, String newEmail) {
        User user = repository.findById(userId);
        user.setEmail(newEmail);
        repository.save(user);
    }
}

// 4. Тестирование
@Test
public void testUpdateUserEmail() {
    // Mock repository вместо реального
    UserRepository mockRepository = mock(UserRepository.class);
    User user = new User(1L, "old@example.com");
    when(mockRepository.findById(1L)).thenReturn(user);
    
    UserService service = new UserService(mockRepository);
    service.updateUserEmail(1L, "new@example.com");
    
    assertEquals("new@example.com", user.getEmail());
    verify(mockRepository).save(user);
}

DIP vs другие принципы SOLID

ПринципЧто говоритКак применяется
S — Single ResponsibilityОдин класс — одна ответственностьРазделить логику
O — Open/ClosedОткрыт для расширения, закрыт для модификацииИспользовать наследование, интерфейсы
L — Liskov SubstitutionПодтипы заменяемыСоблюдать контракты
I — Interface SegregationМного узких интерфейсовНе перегружать интерфейсы
D — Dependency InversionЗависимости от абстракцийИнъекция зависимостей

Инструменты для DIP

// Spring Framework (самый популярный)
@Service
public class MyService {
    @Autowired
    private MyDependency dependency;
}

// Dagger 2 (для Android)
@Component
public interface AppComponent {
    MyService provideMyService();
}

// Guice (Google Dependency Injection)
@Inject
public MyService(MyDependency dependency) {
    this.dependency = dependency;
}

Заключение

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