Ответ
Я реализую инъекцию зависимостей (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");
}
}
Ключевые принципы, которые я соблюдаю:
- Зависимость от абстракций: Класс зависит от интерфейсов (
ILogger,IPaymentGateway), а не от конкретных реализаций. Это следует принципу D (Dependency Inversion) из SOLID. - Неизменяемость: Зависимости сохраняются в полях
readonly, что гарантирует их инициализацию только в конструкторе. - Контейнер зависимостей: В современных фреймворках (ASP.NET Core, Spring) регистрация зависимостей и их автоматическое разрешение происходит в контейнере.
// Регистрация в ASP.NET Core Startup.cs / Program.cs services.AddScoped<ILogger, FileLogger>(); services.AddScoped<IPaymentGateway, StripeGateway>(); services.AddScoped<OrderProcessingService>(); // Контейнер сам разрешит зависимости
Такой подход позволяет легко подменять реализации (например, на моки в юнит-тестах) и управлять жизненным циклом объектов.