Программа курса← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF

DIP – принцип инверсии зависимостей

TODO: отвалидировать – текст собран автоматически из материалов курса на Google Drive, проверьте формулировки и код.

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

Абстракции не должны зависеть от деталей – детали должны зависеть от абстракций.

Смысл: вместо зависимости от конкретных реализаций нужно ориентироваться на интерфейсы.

// Высокоуровневая логика заказа зависит от конкретной СУБД
public class OrderService
{
    private readonly SqlOrderRepository _repository = new SqlOrderRepository();
}

// Зависимость перевёрнута: обе стороны смотрят на абстракцию
public interface IOrderRepository
{
    void Save(Order order);
}

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository) => _repository = repository;
}

public class SqlOrderRepository : IOrderRepository
{
    public void Save(Order order) { }
}
// Высокоуровневая логика заказа зависит от конкретной СУБД
class OrderService {
private:
    SqlOrderRepository repository_;
};

// Зависимость перевёрнута: обе стороны смотрят на абстракцию
class IOrderRepository {
public:
    virtual void Save(const Order& order) = 0;
    virtual ~IOrderRepository() = default;
};

class OrderService {
public:
    explicit OrderService(IOrderRepository& repository) : repository_(repository) {}

private:
    IOrderRepository& repository_;
};

class SqlOrderRepository : public IOrderRepository {
public:
    void Save(const Order& order) override {}
};
// Высокоуровневая логика заказа зависит от конкретной СУБД
type OrderService struct {
	repository *SQLOrderRepository
}

// Зависимость перевёрнута: интерфейс объявляет тот, кто его использует
type OrderRepository interface {
	Save(order Order) error
}

type OrderService struct {
	repository OrderRepository
}

func NewOrderService(repository OrderRepository) *OrderService {
	return &OrderService{repository: repository}
}

type SQLOrderRepository struct{}

func (SQLOrderRepository) Save(order Order) error { return nil }
# Высокоуровневая логика заказа зависит от конкретной СУБД
class OrderService:
    def __init__(self) -> None:
        self._repository = SqlOrderRepository()

# Зависимость перевёрнута: обе стороны смотрят на абстракцию
from typing import Protocol

class OrderRepository(Protocol):
    def save(self, order: "Order") -> None: ...

class OrderService:
    def __init__(self, repository: OrderRepository) -> None:
        self._repository = repository

class SqlOrderRepository:
    def save(self, order: "Order") -> None:
        pass
// Высокоуровневая логика заказа зависит от конкретной СУБД
class OrderService {
    private readonly repository = new SqlOrderRepository();
}

// Зависимость перевёрнута: обе стороны смотрят на абстракцию
interface OrderRepository {
    save(order: Order): void;
}

class OrderService {
    constructor(private readonly repository: OrderRepository) {}
}

class SqlOrderRepository implements OrderRepository {
    save(order: Order): void {}
}
// Высокоуровневая логика заказа зависит от конкретной СУБД
public class OrderService {
    private final SqlOrderRepository repository = new SqlOrderRepository();
}

// Зависимость перевёрнута: обе стороны смотрят на абстракцию
public interface OrderRepository {
    void save(Order order);
}

public class OrderService {
    private final OrderRepository repository;

    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
}

public class SqlOrderRepository implements OrderRepository {
    public void save(Order order) { }
}
// Высокоуровневая логика заказа зависит от конкретной СУБД
class OrderService {
    private val repository = SqlOrderRepository()
}

// Зависимость перевёрнута: обе стороны смотрят на абстракцию
interface OrderRepository {
    fun save(order: Order)
}

class OrderService(private val repository: OrderRepository)

class SqlOrderRepository : OrderRepository {
    override fun save(order: Order) = Unit
}

Что даёт соблюдение

  • Модуль тестируется без базы, сети и очередей. Подставили реализацию в памяти – и тест идёт за миллисекунды, а не за секунды.
  • Реализацию можно заменить, не трогая логику. Переезд с одной СУБД на другую – это новый класс рядом, а не правки в бизнес-коде.
  • Границы модулей становятся явными. Интерфейс – это записанный контракт: видно, что именно старший модуль требует от младшего.
  • Порядок разработки перестаёт быть жёстким. Логику заказа можно писать до того, как готово хранилище: контракт уже есть.

Где здесь «инверсия»

Обычно старший модуль зависит от младшего: сервис знает про базу. После применения принципа интерфейс принадлежит старшему модулю, а младший его реализует – стрелка зависимости разворачивается в обратную сторону. Отсюда и название.

Не путайте принцип с Dependency Injection: DI – это способ передать реализацию внутрь, а DIP – решение о том, на что вообще смотрит зависимость.