В чем разница между внедрением зависимостей (DI) и инверсией зависимостей (DIP)?

«В чем разница между внедрением зависимостей (DI) и инверсией зависимостей (DIP)?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Внедрение зависимостей (Dependency Injection, DI) — это конкретный шаблон проектирования (паттерн) и техника реализации. Его суть в том, что зависимости объекта (сервисы, которые он использует) не создаются им самим, а предоставляются ему извне ("внедряются").

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

DIP состоит из двух утверждений:

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

Как DI помогает реализовать DIP: DI — это механизм, который позволяет легко следовать DIP, передавая зависимости через абстракции (интерфейсы).

Пример без принципов:

// Класс нижнего уровня (деталь)
class FileLogger {
    public function log($message) {
        file_put_contents('app.log', $message, FILE_APPEND);
    }
}

// Класс верхнего уровня жёстко зависит от детали
class OrderService {
    private $logger;

    public function __construct() {
        $this->logger = new FileLogger(); // Прямое создание зависимости
    }
}

Пример с DIP и DI:

// 1. Абстракция (интерфейс), от которой будут зависять оба уровня
interface LoggerInterface {
    public function log($message);
}

// 2. Деталь (модуль нижнего уровня) зависит от абстракции
class FileLogger implements LoggerInterface {
    public function log($message) { /* ... */ }
}

class DatabaseLogger implements LoggerInterface {
    public function log($message) { /* ... */ }
}

// 3. Модуль верхнего уровня зависит от абстракции, а не от детали
class OrderService {
    private $logger;

    // 4. Внедрение зависимости (DI) через конструктор
    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger; // Получаем готовую реализацию
    }

    public function createOrder() {
        // ... логика создания заказа
        $this->logger->log('Order created');
    }
}

// 5. Сборка зависимостей (например, в контейнере)
$logger = new DatabaseLogger(); // Легко заменить реализацию
$orderService = new OrderService($logger);

Итог:

  • DIP — это цель/принцип: "Завись от абстракций".
  • DI — это средство/способ: "Передавай зависимости извне". Используя DI для передачи абстракций, мы автоматически соблюдаем принцип инверсии зависимостей, получая слабосвязанный, тестируемый и гибкий код.