Итак, мы уже разобрались, что зависимость должна приходить в класс снаружи, а не создаваться им самостоятельно. Теперь логично возникает следующий вопрос:

А как именно передавать эти зависимости внутрь?

На самом деле, способов не так много — всего три. Но каждый имеет своё предназначение, свои плюсы и свои опасные моменты.

public class OrderService
{
    private readonly ILogger _logger;

    public OrderService(ILogger logger)
    {
        _logger = logger;
    }
}

Почему именно этот вариант считается «золотым стандартом»? Потому что он очень честно говорит: «Для того чтобы этот класс работал, ему нужен логгер. Без логгера он существовать не может.»

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

public class DamageService
{
    public void ApplyDamage(Character target, IDamageCalculator calculator)
    {
        int damage = calculator.Calculate();
        target.TakeDamage(damage);
    }
}

DamageService не хранит калькулятор урона. Ему он нужен только в момент расчёта — и только для конкретного вызова. Это удобно там, где зависимость является одноразовой, контекстной, или нужна не всегда.

public class Character
{
    public IWeapon? Weapon { get; set; }
}

Персонаж может существовать и без оружия — просто он будет бить руками. Поэтому логично сделать оружие опциональным и выдать его позже.

Плюсы: зависимость можно подменить в любой момент; удобно для опциональных или меняющихся зависимостей.

Минусы: объект может оказаться «неполным» (если свойство забыли установить); сложнее гарантировать корректное состояние.

Поэтому этот способ обычно используют для второстепенных зависимостей, а не для ключевых. Большая ошибка новичков — думать, что DI = «всё через конструктор». Нет. DI — это идея передачи зависимостей извне, а как именно вы передадите — зависит от задачи.

Представим, что у нас есть система авторизации. Сервис, который проверяет логин и пароль, не может работать без хранилища пользователей — это его фундаментальная зависимость.

Пример внедрения через конструктор:

public class AuthService
{
    private readonly IUserRepository _repository;

    public AuthService(IUserRepository repository)
    {
        _repository = repository;
    }

    public bool Login(string username, string password)
    {
        var user = _repository.GetByUsername(username);
        return user != null && user.Password == password;
    }
}

AuthService без хранилища пользователей — просто бесполезен. Поэтому зависимость передаётся через конструктор: она обязательная.

Пример внедрения через метод. Представьте игру, где урон может считаться по-разному: критический удар, магический удар, урон от яда. Сервису нанесения урона не нужно хранить калькулятор урона всё время — он нужен только в момент атаки.

public class CombatService
{
    public void Attack(Character target, IDamageCalculator calculator)
    {
        int damage = calculator.CalculateDamage();
        target.TakeDamage(damage);
    }
}

Пример внедрения через свойство. Допустим, у нас есть персонаж в игре. У него может быть оружие, а может не быть — игрок может снять его, поменять или подобрать новое.

public class Player
{
    public IWeapon? Weapon { get; set; }

    public void Attack()
    {
        if (Weapon == null)
            Console.WriteLine("Игрок бьёт кулаками!");
        else
            Weapon.Hit();
    }
}

Оружие — это не обязательная часть персонажа. Игрок может снять его, поменять, подобрать новое — это зависимость, которая меняется на лету, поэтому её удобно передавать через свойство.

Хотя внедрение через свойства и существует, в реальных проектах оно используется заметно реже. Свойство больше подходит для обычных данных — названия UI-окна, громкости звука, цвета темы. Когда речь идёт именно о поведении или о важной части логики класса, разработчики почти всегда предпочитают метод, ведь метод ясно показывает, что это действие, а не пассивная установка значения.

Constructor Injection — внедрение зависимости через конструктор. Самый распространённый способ — делает зависимость обязательной и явной.

Method Injection — внедрение зависимости через параметр метода. Используется, когда зависимость нужна только на время выполнения одной операции.

Property Injection — внедрение зависимости через публичное свойство. Подходит для опциональных зависимостей, которые могут меняться.

Конструктор — для обязательных зависимостей (золотой стандарт). Метод — для контекстных одноразовых зависимостей. Свойство — для редких опциональных случаев.

Обсуждение урока

0
Комментарии видны всем. Чтобы участвовать в обсуждении, войдите или зарегистрируйтесь.
Модерация сообщества

Пожаловаться на комментарий

Расскажите модераторам, что именно требует внимания.