This lesson is not translated yet – the Russian original is below.
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 – решение о том, на что вообще смотрит зависимость.