Как реализовать инъекцию зависимостей в сервисе?

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

Ответ

Я реализую инъекцию зависимостей (DI) преимущественно через конструктор. Это делает зависимости класса явными, а сам класс — легко тестируемым.

Базовый пример на C#/.NET:

public interface ILogger
{
    void Log(string message);
}

public interface IPaymentGateway
{
    Task<bool> ChargeAsync(Order order);
}

// Сервис с инъекцией зависимостей через конструктор
public class OrderProcessingService
{
    private readonly IPaymentGateway _paymentGateway;
    private readonly ILogger _logger;

    // Зависимости явно объявлены в конструкторе
    public OrderProcessingService(IPaymentGateway paymentGateway, ILogger logger)
    {
        _paymentGateway = paymentGateway ?? throw new ArgumentNullException(nameof(paymentGateway));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task ProcessOrderAsync(Order order)
    {
        _logger.Log($"Processing order {order.Id}");
        bool success = await _paymentGateway.ChargeAsync(order);
        _logger.Log(success ? "Charge successful" : "Charge failed");
    }
}

Ключевые принципы, которые я соблюдаю:

  1. Зависимость от абстракций: Класс зависит от интерфейсов (ILogger, IPaymentGateway), а не от конкретных реализаций. Это следует принципу D (Dependency Inversion) из SOLID.
  2. Неизменяемость: Зависимости сохраняются в полях readonly, что гарантирует их инициализацию только в конструкторе.
  3. Контейнер зависимостей: В современных фреймворках (ASP.NET Core, Spring) регистрация зависимостей и их автоматическое разрешение происходит в контейнере.
    // Регистрация в ASP.NET Core Startup.cs / Program.cs
    services.AddScoped<ILogger, FileLogger>();
    services.AddScoped<IPaymentGateway, StripeGateway>();
    services.AddScoped<OrderProcessingService>(); // Контейнер сам разрешит зависимости

Такой подход позволяет легко подменять реализации (например, на моки в юнит-тестах) и управлять жизненным циклом объектов.