Подтвердить что ты не робот

IoC (Ninject) и заводы

Если у меня есть следующий код:

public class RobotNavigationService : IRobotNavigationService {
  public RobotNavigationService(IRobotFactory robotFactory) {
    //...
  }
}
public class RobotFactory : IRobotFactory {
  public IRobot Create(string nameOfRobot) {
    if (name == "Maximilian") {
      return new KillerRobot(); 
    } else {
      return new StandardRobot();
    }
  }
}

Мой вопрос в том, что это правильный способ сделать Inversion of Control здесь? Я не хочу добавлять белки KillerRobot и StandardRobot в класс Factory? И я не хочу приводить их через IoC.Get < > правильно? bc, что будет Service Location не верно IoC правильно? Есть ли лучший способ подойти к проблеме переключения бетона во время выполнения?

4b9b3361

Ответ 1

Для вашего образца у вас есть прекрасная реализация factory, и я ничего не изменил бы.

Однако я подозреваю, что ваши классы KillerRobot и StandardRobot фактически имеют собственные зависимости. Я согласен с тем, что вы не хотите подвергать свой контейнер IoC RobotFactory.

Один из вариантов заключается в использовании расширения ninject factory:

https://github.com/ninject/ninject.extensions.factory/wiki

Это дает вам два способа ввода фабрик - через интерфейс и путем ввода Func, который возвращает IRobot (или что-то еще).

Пример для интерфейса factory создание: https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

Пример для func: https://github.com/ninject/ninject.extensions.factory/wiki/Func

Если вы захотите, вы также можете сделать это, привязав func к вашему коду инициализации IoC. Что-то вроде:

var factoryMethod = new Func<string, IRobot>(nameOfRobot =>
                        {
                            if (nameOfRobot == "Maximilian")
                            {
                                return _ninjectKernel.Get<KillerRobot>();
                            }
                            else
                            {
                                return _ninjectKernel.Get<StandardRobot>();
                            }

                        });
_ninjectKernel.Bind<Func<string, IRobot>>().ToConstant(factoryMethod);

Теперь ваша служба навигации может выглядеть так:

    public class RobotNavigationService
    {
        public RobotNavigationService(Func<string, IRobot> robotFactory)
        {
            var killer = robotFactory("Maximilian");
            var standard = robotFactory("");
        }
    }

Конечно, проблема с этим подходом заключается в том, что вы пишете методы factory прямо в своей инициализации IoC - возможно, не лучший компромисс...

Расширение factory пытается решить эту проблему, предоставив вам несколько конвенционных подходов - таким образом, вы сможете сохранить нормальную цепочку DI с добавлением контекстно-зависимых зависимостей.

Ответ 2

Я не хочу добавлять контуры KillerRobot и StandardRobot в класс Factory?

Я бы предположил, что вы, вероятно, это сделаете. Какова была бы цель Factory, если бы не создавать конкретные объекты? Я думаю, что я могу видеть, откуда вы пришли - если IRobot описывает контракт, разве контейнер для инъекций не должен отвечать за его создание? Разве это не те контейнеры?

Может быть. Однако возвращение конкретных заводов, ответственных за объекты new, кажется довольно стандартным образцом в мире IoC. Я не думаю, что это против принципа, чтобы конкретный Factory выполнял какую-то фактическую работу.

Ответ 3

Как вы должны:

kernel.Bind<IRobot>().To<KillingRobot>("maximilliam");
kernel.Bind<IRobot>().To<StandardRobot>("standard");
kernel.Bind<IRobotFactory>().ToFactory();

public interface IRobotFactory
{
    IRobot Create(string name);
}

Но таким образом я думаю, что вы потеряете нулевое имя, поэтому при вызове IRobotFactory.Create вы должны убедиться, что правильное имя отправлено через параметр.

При использовании ToFactory() в привязке интерфейса все, что он делает, это создать прокси-сервер, используя Castle (или динамический прокси), который получает IResolutionRoot и вызывает Get().

Ответ 4

Я искал способ очистить массивный оператор switch, который возвратил класс С# для выполнения некоторой работы (запах кода здесь).

Я не хотел явно сопоставлять каждый интерфейс с его конкретной реализацией в модуле ninject (по существу, это напоминание о длинном случае коммутатора, но в файле diff), поэтому я настраиваю модуль для автоматического связывания всех интерфейсов:

public class FactoryModule: NinjectModule
{
    public override void Load()
    {
        Kernel.Bind(x => x.FromThisAssembly()
                            .IncludingNonPublicTypes()
                            .SelectAllClasses()
                            .InNamespaceOf<FactoryModule>()
                            .BindAllInterfaces()
                            .Configure(b => b.InSingletonScope()));
    }
}

Затем создайте класс factory, реализующий StandardKernal, который будет получать указанные интерфейсы и их реализации через экземпляр singleton с использованием IKernal:

    public class CarFactoryKernel : StandardKernel, ICarFactoryKernel{
    public static readonly ICarFactoryKernel _instance = new CarFactoryKernel();

    public static ICarFactoryKernel Instance { get => _instance; }

    private CarFactoryKernel()
    {
        var carFactoryModeule = new List<INinjectModule> { new FactoryModule() };

        Load(carFactoryModeule);
    }

    public ICar GetCarFromFactory(string name)
    {
        var cars = this.GetAll<ICar>();
        foreach (var car in cars)
        {
            if (car.CarModel == name)
            {
                return car;
            }
        }

        return null;
    }
}

public interface ICarFactoryKernel : IKernel
{
    ICar GetCarFromFactory(string name);
}

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

например:.

    public interface ICar
{
    string CarModel { get; }
    string Drive { get; }
    string Reverse { get; }
}

public class Lamborghini : ICar
{
    private string _carmodel;
    public string CarModel { get => _carmodel; }
    public string Drive => "Drive the Lamborghini forward!";
    public string Reverse => "Drive the Lamborghini backward!";

    public Lamborghini()
    {
        _carmodel = "Lamborghini";
    }
}

Использование:

        [Test]
    public void TestDependencyInjection()
    {
        var ferrari = CarFactoryKernel.Instance.GetCarFromFactory("Ferrari");
        Assert.That(ferrari, Is.Not.Null);
        Assert.That(ferrari, Is.Not.Null.And.InstanceOf(typeof(Ferrari)));

        Assert.AreEqual("Drive the Ferrari forward!", ferrari.Drive);
        Assert.AreEqual("Drive the Ferrari backward!", ferrari.Reverse);

        var lambo = CarFactoryKernel.Instance.GetCarFromFactory("Lamborghini");
        Assert.That(lambo, Is.Not.Null);
        Assert.That(lambo, Is.Not.Null.And.InstanceOf(typeof(Lamborghini)));

        Assert.AreEqual("Drive the Lamborghini forward!", lambo.Drive);
        Assert.AreEqual("Drive the Lamborghini backward!", lambo.Reverse);
    }