目前我正在重构一个旧项目,并尝试使用Ninject实现依赖项注入。
我遇到了一个或多或少很简单的问题。我找到了一个有效的解决方案,但我不确定这是否是解决这个问题的最佳方法。
我试图尽可能准确地解释情况:
我有一个接口ITool
:
public interface ITool
{
string Caption { get; set; }
string Name { get; }
IAction[] Actions { get; }
}
还有一个抽象类AbstractTool
(具体内容与问题无关)
此外,我还有派生类GenericTool
:
public class GenericTool : AbstractTool
{
private readonly string furtherInformation;
public override string FurtherInformation
{
get { return furtherInformation; }
}
public GenericTool (string furtherInformation, string caption, string name, Action[] actions)
: base(caption, name, actions)
{
this.furtherInformation= furtherInformation;
}
}
有一次,这个GenericTool
被使用并实例化了几次,如下所示:
new OtherObject(
{
new List<GenericTool>
{
new GenericTool("info1","caption1","name1", new IActions[]
{ new Action1(), new Action2()}),
new GenericTool("info2","caption2","name2", new IActions[]
{ new Action3(), new Action4()}),
...
}
}...
我只想通过一个电话解决这个问题:
kernel.Get<OtherObject>();
但我不知道如何将它们将正确解析的不同操作(Action1、2、3、4)绑定到所属的GenericTool
。
我的解决方案是创建GenericTool
的派生类,并使用Ninject 的NamedAttribute
public class SpecialTool1 : GenericTool
{
public SpecialTool1([Named("SpecialTool1Action")] IAction[] actions)
: base("info1", "caption1","name1", actions)
{}
}
public class SpecialTool2 : GenericTool
{
public SpecialTool2([Named("SpecialTool2Action")] IAction[] actions)
: base("info2", "caption2","name2", actions)
{}
}
行动的绑定:
Bind<IAction>.To<Action1>().Named("SpecialTool1Action");
Bind<IAction>.To<Action2>().Named("SpecialTool1Action");
Bind<IAction>.To<Action3>().Named("SpecialTool2Action");
Bind<IAction>.To<Action4>().Named("SpecialTool2Action");
这样我就得到了要求的结果。但是我必须创建很多像SpecialTool1
这样的小类。
所以我的第二项工作是:
Bind<IAction>.To<Action1>().Named("SpecialTool1Action");
Bind<IAction>.To<Action2>().Named("SpecialTool1Action");
Bind<ITool>().To<GenericTool>()
.WithConstructorArgument("furtherInformation","info1")
.WithConstructorArgument("caption", "caption1")
.WithConstructorArgument("name", "name1")
.WithConstructorArgument("actions", GetLateNamedConstructorArgument);
函数GetLateNamedConstructorArgument
:
private IAction[] GetLateNamedConstructorArgument(IContext context)
{
IEnumerable<IAction> actions= context.Kernel.
GetAll<IAction>("SpecialTool1Action");
return actions.ToArray();
}
对于最后一个解决方案,我在没有创建几十个类的情况下得到了相同的结果。
但有更好的方法来解决这个问题吗?我可以用其他方式声明所需绑定的名称吗?是否还有其他完整的工作?
编辑
我想是这样的:
Bind<ITool>().To<GenericTool>()
...
.WithConstructorArgument("actions", [Named("SpecialTool1Action")]);
并且Ninject将所有操作注入SpecialTool1Action
名称。
走这条路的问题是,正如命名绑定的文档中提到的,它通常是服务位置反模式。
这里更好的方法是使用抽象工厂模式。
抽象工厂模式提供了一种封装具有共同主题但未指定其混凝土等级。
基本上,您不是让DI容器在代码中传播以满足复杂的需求,而是从中请求工厂并使用它们来创建所需的实例。
Ninject提供了一些实现这一点的方法,例如实现您自己的Providers或Factory方法。
在Ninjectv3中,您可以使用Factory接口扩展。这个文档链接有一大堆示例代码。以下是主要的一个来说明这个概念:
public class Foo
{
readonly IBarFactory barFactory;
public Foo(IBarFactory barFactory)
{
this.barFactory = barFactory;
}
public void Do()
{
var bar = this.barFactory.CreateBar();
...
}
}
public interface IBarFactory
{
Bar CreateBar();
}