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





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