Инверсия управления и контейнеров IoC

Я понимаю всю концепцию инверсии управления, но изо всех сил пытаюсь понять, где находится контейнер IoC и как он может помочь.

Вот пример. Допустим, у нас есть следующие интерфейсы ...

public interface IWarrior
{
    string Name { get; }
    IWeapon Weapon { get; }
    void EquipWeapon(IWeapon weapon);
    void Attack(ITarget target);
    void DoVictoryDance();
}

public interface IWeapon
{
    int AttackPower { get; }
}

public interface ITarget
{
    int Health { get; }
    int ArmorValue { get; }
    int ReceiveAttack(int damage);
}

Базовый класс IWarrior ...

public abstract class BaseWarrior : IWarrior
{
    private static readonly Random Random = new Random();

    protected BaseWarrior(string name, IWeapon weapon)
    {
        Name = name;
        Weapon = weapon;
    }

    public string Name { get; }
    public IWeapon Weapon { get; private set; }

    public virtual void Attack(ITarget target)
    {
        var attackValue = Random.Next(0, Weapon.AttackPower + 1);

        var damageDone = target.ReceiveAttack(attackValue);

        Console.WriteLine($"{Name} did {damageDone} to {target.GetType().Name}");

        if (target.Health <= 0)
            DoVictoryDance();
    }

    public virtual void EquipWeapon(IWeapon weapon)
    {
        Weapon = weapon;
        Console.WriteLine($"{Name} equips {weapon.GetType().Name}");
    }

    public abstract void DoVictoryDance();
}

Конкретные типы воинов ...

    public class Samurai : BaseWarrior
{
    public Samurai(string name, IWeapon weapon) : base(name, weapon)
    {
    }

    public override void DoVictoryDance()
    {
        Console.WriteLine($"{Name} dances on top of the corpses of his foes.");
    }
}

public class ChuckNorris : BaseWarrior
{
    public ChuckNorris() : base("Chuck Norris", null)
    {
    }

    public override void Attack(ITarget target)
    {
        var targetName = target.GetType().Name;
        Console.WriteLine($"Chuck Norris stares at {targetName}. {targetName} collapses on the floor, dead. {targetName} never saw Chuck Norris.");

        DoVictoryDance();
    }

    public override void EquipWeapon(IWeapon weapon)
    {
        Console.WriteLine("Chuck Norris needs no weapons you fool!");
    }

    public override void DoVictoryDance()
    {
        Console.WriteLine("Chuck Norris doesn't dance. He stares at you until you do it for him.");
    }
}

Следующее оружие ...

public class Sword : IWeapon
{
    public int AttackPower => 10;
}

public class CrossBow : IWeapon
{
    public int AttackPower => 15;
}

И, наконец, основной враг:

public class Bear : ITarget
{
    public int Health { get; private set; } = 100;
    public int ArmorValue => 2;
    public int ReceiveAttack(int damage)
    {
        var damageTaken = damage - ArmorValue;

        if (damageTaken > 0)
            Health -= damageTaken;

        return damageTaken;
    }
}

Мне понятно, что это за инверсия управления. Моему основному классу не нужно знать конкретный тип IWarrior, IWarrior никогда не знает конкретный тип IWeapon или ITarget.

Так что это позволяет мне делать что-то вроде ...

ITarget target = new Bear();
IWarrior warrior = new ChuckNorris();
warrior.Attack(target);

или ...

ITarget target = new Bear()
IWarrior warrior = new Samurai("Ben", new Sword());
warrior.Attack(target);

И я вижу, как было бы проще иметь контейнер, в котором хранятся только мои объекты, чтобы я не «терял их из виду» (например, не хочу, чтобы много мечей или оружия летало, также это могут быть синглтоны. ).

Примеры контейнеров IoC, которые я видел, показывают нечто подобное:

IocContainer container = new IocContainer();
container.Bind<IWarrior>().To<Samurai>();
container.Bind<IWeapon>().To<Sword>();
container.Bind<ITarget>().To<Bear>();

а затем сделать что-то вроде ...

ITarget target = container.Get<ITarget>();
IWarrior warrior = container.Get<IWarrior>();
warrior.Attack(target);

Но теперь я в основном говорю, что мой IWarrior всегда является Samurai, что мой IWeapon всегда является Sword и что мой ITarget всегда является Bear.

Это не совсем то, что я хотел! Я хочу иметь возможность создавать разные типы IWarrior с разными комбинациями IWeapon и атаковать множество ITargets!

Возможно, я неправильно понимаю что-то базовое о том, как работает контейнер IoC, но я видел несколько видеороликов о них (с использованием нескольких библиотек), и все они, похоже, в основном говорят когда мне нужно «что-то», реализующее интерфейс XX, найди в вашем контейнере объект конкретного типа YY. Если у вас его еще нет, создайте его, сохраните и отправьте мне.

Может ли кто-нибудь объяснить, в чем заключаются преимущества контейнера IoC, привести реальные примеры того, как он работает, и как можно сохранить выбор (не ограничивая IWarrior до Samurai, например, как указано выше)?

Я думаю, что ваш сценарий - плохой пример, поскольку вы использовали бы IoC в основном для внедрения зависимостей, а затем для служб, а не для бизнес-объектов. Идея состоит в том, что у вас есть класс, который зависит от других классов, и у них есть дополнительные зависимости. Без IoC вам всегда нужно было бы знать, какую конкретную реализацию использовать, и было бы сложно заменить одну реализацию другой без изменения каждого использования. С IoC у вас есть центральное место, где вы регистрируете свои реализации, и вы просто указываете в конструкторе, какие интерфейсы вы хотите, но редко вручную с помощью Get.

ckuri 29.11.2018 19:11

@ckuri Я думаю, что в качестве примера DI / IoC это хороший пример (учитывая, что IoC - очень расплывчатый термин). В качестве кандидата на контейнер IoC это может не быть, но, опять же, поскольку у меня возникли проблемы с пониманием того, что именно контейнер IoC обеспечивает, этого можно было бы ожидать. Можете ли вы предоставить объяснение (с включенным кодом) соответствующего примера, в котором контейнер IoC может помочь и почему мой пример является плохим примером? Если ответят на мои вопросы, я отмечу ответ ...

cogumel0 29.11.2018 21:45

@ cogumel0 Я столкнулся с теми же проблемами, и это чтение мне помогает: Как выбрать реализацию службы по контексту? В основном какой-то контейнер IoC может позволить вам выбрать, какой компонент используется в другом экземпляре

NicoD 30.11.2018 18:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
56
0

Другие вопросы по теме