使用 Ninject.Factory 比仅仅注入 IKernel 有什么好处?
What is the benefit of using a Ninject.Factory over just injecting the IKernel?
根据 this article(第一段),将 IKernel 注入任何需要它的地方是不好的做法。
相反,建议引入一个由 Ninject 自动实现的工厂接口(在内部进行相同的解析)。
这是我正在处理的实际代码片段:
以前的实现:
public class CommandServer
{
[Inject]
public IKernel Kernel { get; set; }
....
public TResponse ExecuteCommand<TRequest, TResponse>(TRequest request)
where TResponse : ResponseBase, new()
{
...
var command = Kernel.Get<ICommand<TRequest, TResponse>>();
...
}
}
使用工厂:
public class CommandServer
{
[Inject]
public ICommandFactory CommandFactory { get; set; }
....
public TResponse ExecuteCommand<TRequest, TResponse>(TRequest request)
where TResponse : ResponseBase, new()
{
...
var command = CommandFactory.CreateCommand<TRequest, TResponse>();
...
}
}
// at binding time:
public interface ICommandFactory
{
ICommand<TRequest, TResponse> CreateCommand<TRequest, TResponse>();
}
Bind<ICommandFactory>().ToFactory();
我并不是说我不喜欢它(它看起来又好又干净)- 只是不确定为什么前者特别糟糕而后者好得多?
通常您不应该使用服务定位器模式。你为什么问?请参阅 Mark Seeman(comments, too!) and this SO question。使用 IKernel
(或更好:仅 IResolutionRoot
部分)闻起来 像 服务定位器。
现在 Mark 建议您改为应用 Abstract Factory Pattern - 他还提到了动态代理方法。
我个人认为使用 ninject 自动生成的工厂(= 动态代理方法)是值得的。
你应该不使用像这样的工厂:
public interface IServiceLocator
{
T Create<T>();
}
因为……它是服务定位器 ;-)
但是,使用
public interface IResponseHandleFactory
{
IResponseHandle Create(int responseId);
}
完全没问题。
当然,您也可以直接使用 IResolutionRoot
来完成此操作 - 而不是工厂。代码如下所示:
IResolutionRoot.Get<IResponseHandle>(
new ConstructorArgument("responseId", theResponseIdValue);
不直接使用IResolutionRoot
的原因
- 很多
IResolutionRoot
"methods" 实际上是扩展方法。这使单元测试变得复杂很多(如果你想对它进行单元测试,这基本上不是一个明智的选择)。
- 轻微 与容器的解耦(=> 易于更改 DI 容器)比使用工厂接口时更差。您还可以将自动生成的工厂功能实现为对其他容器的附加——如果它们还没有(我已经为 Unity 和 AutoFac 亲自这样做了)。然而,它需要一些关于动态代理的知识。
工厂接口的替代方法:使用 Func<>
工厂。上面的例子也可以用Func<int, IResponseHandle>()
代替。很多 DI 容器开箱即用/使用标准插件支持这一点(ninject 需要 Factory 扩展)。所以你会更加脱离容器。缺点:难以进行单元测试且参数命名不明确。
根据 this article(第一段),将 IKernel 注入任何需要它的地方是不好的做法。
相反,建议引入一个由 Ninject 自动实现的工厂接口(在内部进行相同的解析)。
这是我正在处理的实际代码片段:
以前的实现:
public class CommandServer
{
[Inject]
public IKernel Kernel { get; set; }
....
public TResponse ExecuteCommand<TRequest, TResponse>(TRequest request)
where TResponse : ResponseBase, new()
{
...
var command = Kernel.Get<ICommand<TRequest, TResponse>>();
...
}
}
使用工厂:
public class CommandServer
{
[Inject]
public ICommandFactory CommandFactory { get; set; }
....
public TResponse ExecuteCommand<TRequest, TResponse>(TRequest request)
where TResponse : ResponseBase, new()
{
...
var command = CommandFactory.CreateCommand<TRequest, TResponse>();
...
}
}
// at binding time:
public interface ICommandFactory
{
ICommand<TRequest, TResponse> CreateCommand<TRequest, TResponse>();
}
Bind<ICommandFactory>().ToFactory();
我并不是说我不喜欢它(它看起来又好又干净)- 只是不确定为什么前者特别糟糕而后者好得多?
通常您不应该使用服务定位器模式。你为什么问?请参阅 Mark Seeman(comments, too!) and this SO question。使用 IKernel
(或更好:仅 IResolutionRoot
部分)闻起来 像 服务定位器。
现在 Mark 建议您改为应用 Abstract Factory Pattern - 他还提到了动态代理方法。
我个人认为使用 ninject 自动生成的工厂(= 动态代理方法)是值得的。 你应该不使用像这样的工厂:
public interface IServiceLocator
{
T Create<T>();
}
因为……它是服务定位器 ;-)
但是,使用
public interface IResponseHandleFactory
{
IResponseHandle Create(int responseId);
}
完全没问题。
当然,您也可以直接使用 IResolutionRoot
来完成此操作 - 而不是工厂。代码如下所示:
IResolutionRoot.Get<IResponseHandle>(
new ConstructorArgument("responseId", theResponseIdValue);
不直接使用IResolutionRoot
的原因
- 很多
IResolutionRoot
"methods" 实际上是扩展方法。这使单元测试变得复杂很多(如果你想对它进行单元测试,这基本上不是一个明智的选择)。 - 轻微 与容器的解耦(=> 易于更改 DI 容器)比使用工厂接口时更差。您还可以将自动生成的工厂功能实现为对其他容器的附加——如果它们还没有(我已经为 Unity 和 AutoFac 亲自这样做了)。然而,它需要一些关于动态代理的知识。
工厂接口的替代方法:使用 Func<>
工厂。上面的例子也可以用Func<int, IResponseHandle>()
代替。很多 DI 容器开箱即用/使用标准插件支持这一点(ninject 需要 Factory 扩展)。所以你会更加脱离容器。缺点:难以进行单元测试且参数命名不明确。