如果我有以下代码:
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();
}
}
}
我的问题是在这里进行控制反转的正确方法是什么?我不想将KillerRobot和StandardRobot混凝土添加到Factory类吗?我不想通过IoC.Get<>带来它们。对?那个服务位置不是真的IoC对吗?有没有更好的方法来解决在运行时切换具体问题的问题?
答案 0 :(得分:33)
对于您的样品,您有一个非常好的工厂实施,我不会改变任何东西。
但是,我怀疑你的KillerRobot和StandardRobot类实际上有自己的依赖。我同意您不希望将IoC容器暴露给RobotFactory。
一种选择是使用ninject工厂扩展名:
https://github.com/ninject/ninject.extensions.factory/wiki
它为您提供了两种注入工厂的方法 - 通过接口,以及注入一个返回IRobot(或其他)的Func。
基于界面的工厂创建示例:https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface
基于func的示例:https://github.com/ninject/ninject.extensions.factory/wiki/Func
如果您愿意,也可以通过在IoC初始化代码中绑定func来实现。类似的东西:
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("");
}
}
当然,这种方法的问题在于你在IoC初始化中编写工厂方法 - 也许不是最好的权衡......
工厂扩展试图通过为您提供几种基于约定的方法来解决这个问题 - 从而允许您通过添加上下文相关的依赖项来保留正常的DI链。
答案 1 :(得分:3)
我不想将KillerRobot和StandardRobot混凝土添加到Factory类吗?
我建议你这样做。如果不实例化具体对象,工厂的目的是什么?我想我可以看到你来自哪里 - 如果IRobot
描述合同,注射容器不应该负责创建合同吗?这不是容器的用途吗?
也许。但是,返回负责new
对象的具体工厂似乎是IoC世界中非常标准的模式。我认为让一个具体的工厂做一些实际的工作是不符合原则的。
答案 2 :(得分: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()
时,它所做的只是使用接收IResolutionRoot并调用Get()的Castle(或动态代理)创建代理。
答案 3 :(得分:0)
我一直在寻找一种清理大量switch语句的方法,该语句返回一个C#类来完成一些工作(代码味道在这里)。
我不想在ninject模块中明确地将每个接口映射到其具体实现(基本上是冗长的切换案例的模仿,但是在diff文件中)所以我设置模块自动绑定所有接口:
public class FactoryModule: NinjectModule
{
public override void Load()
{
Kernel.Bind(x => x.FromThisAssembly()
.IncludingNonPublicTypes()
.SelectAllClasses()
.InNamespaceOf<FactoryModule>()
.BindAllInterfaces()
.Configure(b => b.InSingletonScope()));
}
}
然后创建工厂类,实现StandardKernal,它将使用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实现可以通过您在装饰类的界面上选择的标识符来获取任何接口。
e.g:
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);
}