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 правильно? Есть ли лучший способ подойти к проблеме переключения бетона во время выполнения?
Ответы
Ответ 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);
}