摘要

如果我们已经知道了一个类所有的依赖项,在我们只需要依赖项的一个实例的场景中,在类的构造函数中引入一系列的依赖项是容易的。但是有些情况,我们需要在一个类里创建依赖项的多个实例,这时候Ninject注入就不够用了。也有些情况,我们不知道一个消费者可能需要哪个服务,因为他可能在不同的场合下需要不同的服务,而且在创建类的时候实例化所有依赖项也不合理。这样的情况,动态工厂可以帮忙。我们可以设计我们的类让他依赖一个工厂,而不是依赖这个工厂能够创建的对象。然后,我们能够命令工厂去通过命令创建需要的类型和任意需要的数量。下面两个例子解决上面两个问题。Ninject动态工厂创建指定数量的依赖项和创建指定类型的依赖项。

例子:形状工厂

附:代码下载

在第一个例子中,我们将创建一个图形动态库。它包含一个ShapService类,提供一个AddShapes方法来给指定的ICanvas对象添加指定数量具体的IShape对象:

     public void AddShapes(int circles, int squares, ICanvas canvas)
{
for (int i = ; i < circles; i++)
{
var circle = new Circle();
canvas.AddShap(circle);
}
for (int i = ; i < squares; i++)
{
var square = new Square();
canvas.AddShap(square);
}
}

传统的方法是直接在AddShapes方法里创建新的Circle和Square类实例。然而,这个方法我们将ShapService类和具体的Circle和Square类耦合起来,这和DI原则相反。另外,通过参数引入这些依赖项不符合我们的需求,因为那样一个形状只注入一个实例,这样不够。为了解决这个问题,我们应该像下面首先创建一个简单工厂接口:

 public interface IShapeFactory
{
ICircle CreateCircle();
ISquare CreateSquare();
}

然后,我们可以引入这个工厂接口作为ShapeService类的依赖项。

     public class ShapeService
{
private readonly IShapeFactory _factory; public ShapeService(IShapeFactory factory)
{
this._factory = factory;
} public void AddShapes(int circles, int squares, ICanvas canvas)
{
for (int i = ; i < circles; i++)
{
var circle = _factory.CreateCircle();
canvas.AddShap(circle);
}
for (int i = ; i < squares; i++)
{
var square = _factory.CreateSquare();
canvas.AddShap(square);
}
}
}

好消息是我们不需要担心怎样实现IShapeFactory。Ninject能够动态地实现它,再注入这个实现的工厂到这个ShapeService类。我们只需要添加下面的代码到我们类型注册部分:

   Bind<IShapeFactory>().ToFactory();
  Bind<ISquare>().To<Square>();
  Bind<ICircle>().To<Circle>();

为了使用Ninject工厂,我们需要添加Ninject.Extensions.Factory动态库的引用。可以通过NuGet添加,也可以通过从Ninject官方网站上下载。

记住工厂可以有需要的尽可能多的方法,每个方法可以返回任意需要的类型。这些方法可以有任意的名字,有任意数量的参数。唯一的限制是名字和参数类型必须跟具体类名字和构造函数参数的类型一致,但是跟他们的顺序没关系。甚至参数的数量都不需要一致,Ninject将试着解析那些没有通过工厂接口提供的参数。

因此,如果具体Square类是下面这样:

 public class Square
{
public Square(Point startPoint, Point endPoint)
{ ... }
}

这个IShapeFactory工厂接口就应该像下面这样:

 public interface IShapeFactory
{
ICircle CreateCircle();
ISquare CreateSquare(Point startPoint, Point endPoint);
}

或者,CreateSquare方法可能像下面这样:

 ISquare CreateSquare(Point endPoint, Point startPoint);

这是Ninject动态工厂默认的行为。然而,通过创建自定义实例提供者,默认行为可以被重写。后面的文章将要介绍这个。

对动态工厂注册基于约定的绑定和常规的约定绑定稍微有点不同。不同在于,一旦我们选择了程序集,我们应该选择服务类型而不是组件,然后绑定他们到工厂。下面描述怎样实现这两个步骤。

  1. 选择服务类型

使用下面的方法选择一个抽象类或接口:

  • SelectAllIncludingAbstractClasses(): 这个方法选择所有的类,包括抽象类。
  • SelectAllAbstractClasses(): 这个方法只选择抽象类。
  • SelectAllInterfaces(): 这个方法选择所有接口。
  • SelectAllTypes(): 这个方法选择所有类型(类、接口、结构、共用体和原始类型)

下面的代码绑定选择的程序集下的所有接口到动态工厂:

     kernel.Bind(x => x
.FromAssembliesMatching("factories")
.SelectAllInterfaces()
.BindToFactory());

  2. 定义绑定生成器

使用下面的方法定义合适的绑定生成器:

  • BindToFactory: 这个方法注册映射的类型作为动态工厂。
  • BindWith: 这个方法使用绑定生成器参数创建绑定。创建一个绑定生成器只是关于实现IBindingGenerator接口的问题

下面的例子绑定当前程序集中所有那些以Factory结尾的接口到动态工厂。

     kernel.Bind(x => x
.FromThisAssembly()
.SelectAllInterfaces()
.EndingWith("Factory")
.BindToFactory());

例子:电信交换机

附:代码下载

在下面的例子中,我们将为电信中心写一个服务,这个服务返回指定交换机当前状态信息。电信交换机生产于不同的厂家,可能提供不同的方法查询状态。一些支持TCP/IP协议通信,一些只是简单地将状态写入一个文件。

先按下面这样创建Switch类:

     public class Switch
{
public string Name { get; set; }
public string Vendor { get; set; }
public bool SupportsTcpIp { get; set; }
}

收集交换机状态像下面创建一个接口:

     public interface IStatusCollector
{
string GetStatus(Switch @switch);
}

为两种不同的交换机类型,我们需要对这个接口的两个不同的实现。支持TCP/IP通信的交换机和那些不支持的。分别创建TcpStatusCollector类和FileStatusCollector类:

     public class TcpStatusCollector : IStatusCollector
{
public string GetStatus(Switch @switch)
{
System.Console.WriteLine("TCP Get Status");
return "TCP Status";
}
} public class FileStatusCollector : IStatusCollector
{
public string GetStatus(Switch @switch)
{
System.Console.WriteLine("File Get Status");
return "File Status";
}
}

我们还需要声明一个可以创建者两种具体StatusCollector实例的工厂接口:

     public interface IStatusCollectorFactory
{
IStatusCollector GetTcpStatusCollector();
IStatusCollector GetFileStatusCollector();
}

最后是SwitchService类:

     public class SwitchService
{
private readonly IStatusCollectorFactory factory; public SwitchService(IStatusCollectorFactory factory)
{
this.factory = factory;
} public string GetStatus(Switch @switch)
{
IStatusCollector collector;
if (@switch.SupportsTcpIp)
{
collector = factory.GetTcpStatusCollector();
}
else
{
collector = factory.GetFileStatusCollector();
}
return collector.GetStatus(@switch);
}
}

这个SwitchService类将绝不会创建一个FileStatusCollector实例,如果所有给定的交换机都支持TCP/IP。按这种方法,SwitchService类只注入他真实需要的依赖项,而不是所有他可能需要的依赖项。

IStatusCollectorFactory有两个工厂方法,两个都返回相同的类型。现在,Ninject这个工厂的实现如何理解怎样解析IStatusCollector?魔法在于工厂方法的名字。无论何时工厂方法的名字以Get开头,它指明这个类型将用名称绑定来解析,类型名称就是方法名后面那一串。例如,如果工厂方法名称是GetXXX,这个工厂将试着去找一个名称为XXX的绑定。因此,这个例子的类型注册段应该像下面这样:

         Kernel.Bind(x => x.FromThisAssembly()
.SelectAllInterfaces()
.EndingWith("Factory")
.BindToFactory()); Kernel.Bind(x => x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<IStatusCollector>()
.BindAllInterfaces()
.Configure((b, comp) => b.Named(comp.Name)));

第一个约定绑定那些名称以Factory结尾的接口到工厂。

第二个为所有的IStatusCollector的实现注册名称绑定,按这种方式,每个绑定用他的组件名称命名。它等同于下面单独的两行绑定:

         Kernel.Bind<IStatusCollector>().To<TcpStatusCollector>().Named("TcpStatusCollector");
Kernel.Bind<IStatusCollector>().To<FileStatusCollector>().Named("FileStatusCollector");

然而,使用这样的单独绑定依赖于字符串名称,这很容易出错,这些关系可能很容易被拼写错误打断。有另一种特别为这种情况设计的单独绑定命名的方式,当引用了Ninject.Extensions.Factory时才可用。我们可以使用NamedLikeFactoryMethod这个帮助方法而不用Named帮助方法来为一个工厂绑定命名:

         Kernel.Bind<IStatusCollector>().To<TcpStatusCollector>().NamedLikeFactoryMethod((IStatusCollectorFactory f) => f.GetTcpStatusCollector());
Kernel.Bind<IStatusCollector>().To<FileStatusCollector>().NamedLikeFactoryMethod((IStatusCollectorFactory f) => f.GetFileStatusCollector());

他意思是说我们在用工厂方法建议的名称定义一个名称绑定。

请注意使用约定经常是首选的方式。

自定义实例提供者

动态工厂不直接实例化请求的类型。然而,它使用另一个称为实例提供者的对象(不要把他跟提供者混淆)来创建一个类型的实例。一些关于工厂方法的信息提供给了这个实例提供者。基于哪一个实例提供者应该来解析请求对象,这些信息包含方法名称、它的返回类型和它的参数。如果一个工厂没有赋给一个自定义实例提供者,它将使用它默认的实例提供者,名称是StandardInstanceProvider。我们可以在注册的时候赋给一个自定义实例提供者到一个工厂,像下面这样:

     Kernel.Bind(x => x.FromThisAssembly()
.SelectAllInterfaces()
.EndingWith("Factory")
.BindToFactory(() => new MyInstanceProvider()));

为了使Ninject接受一个类作为一个实例提供者,实现IInstanceProvider接口的类就足够了。然而,更简单的方法是继承StandardInstanceProvider类并重载相应的成员。

下面的代码显示如何定义一个实例提供者,从NamedAttribute得到绑定名称,而不是从方法名称:

     public class NameAttributeInstanceProvider : StandardInstanceProvider
{
protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
{
var nameAttribute = methodInfo
.GetCustomAttributes(typeof(NamedAttribute), true)
.FirstOrDefault() as NamedAttribute;
if (nameAttribute != null)
{
return nameAttribute.Name;
}
return base.GetName(methodInfo, arguments);
}
}

使用自定义实例提供者,我们能够选择任意名称作为我们的工厂名称,然后使用一个特性来指定请求的绑定名称。

由于Ninject的NamedAttribute特性不能运用在方法上,我们需要创建我们自己的特性:

 public class BindingNameAttribute : Attribute
{
public BindingNameAttribute(string name)
{
this.Name = name;
}
public string Name { get; set; }
}

NameAttributeInstanceProvider改为下面这样:

     public class NameAttributeInstanceProvider : StandardInstanceProvider
{
protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
{
var nameAttribute = methodInfo
.GetCustomAttributes(typeof(BindingNameAttribute), true)
.FirstOrDefault() as BindingNameAttribute;
if (nameAttribute != null)
{
return nameAttribute.Name;
}
return base.GetName(methodInfo, arguments);
}
}

工厂接口现在可以像下面这样定义:

     public interface IStatusCollectorFactory
{
[BindingName("TcpCollector")]
IStatusCollector GetTcpCollector(); [BindingName("FileCollector")]
IStatusCollector GetFileCollector();
}

工厂类型注册应该变成下面这样:

     Kernel.Bind(x => x.FromThisAssembly()
.SelectAllInterfaces()
.EndingWith("Factory")
.BindToFactory(() => new NameAttributeInstanceProvider()));

IStatusCollector注册应该改成下面这样:

         Kernel.Bind<IStatusCollector>().To<TcpStatusCollector>().Named("TcpCollector");
Kernel.Bind<IStatusCollector>().To<FileStatusCollector>().Named("FileCollector");

或者下面这样:

         Kernel.Bind<IStatusCollector>().To<TcpStatusCollector>().NamedLikeFactoryMethod((IStatusCollectorFactory f) => f.GetTcpCollector());
Kernel.Bind<IStatusCollector>().To<FileStatusCollector>().NamedLikeFactoryMethod((IStatusCollectorFactory f) => f.GetFileCollector());

Ninject之旅之十一:Ninject动态工厂(附程序下载)的更多相关文章

  1. Ninject之旅之十二:Ninject在Windows Form程序上的应用(附程序下载)

    摘要: 下面的几篇文章介绍如何使用Ninject创建不同类型的应用系统.包括: Windows Form应用系统 ASP.NET MVC应用系统 ASP.NET Web Form应用系统 尽管对于不同 ...

  2. Ninject之旅之五:Ninject XML配置

    摘要 使用XML配置,需要添加Ninject XML扩展的引用.下一步是添加一个或多个包含类型注册的XML文件.记得这些文件应该跟应用程序一起发布.因此不要忘记将XML文件的属性设置成“Copy if ...

  3. Ninject之旅之十三:Ninject在ASP.NET MVC程序上的应用(附程序下载)

    摘要: 在Windows客户端程序(WPF和Windows Forms)中使用Ninject和在控制台应用程序中使用Ninject没什么不同.在这些应用程序里我们不需要某些配置用来安装Ninject, ...

  4. Ninject之旅之八:Ninject插件模型(附程序下载)

    摘要 在前面的章节中,我们看了在单一的绑定条件下Ninject能够处理依赖类型,就是说,每个服务类型只绑定到单一的实现类型.然而,有些情况下我们需要绑定一个抽象服务类型到多个实现,这叫多个绑定.多个绑 ...

  5. Ninject之旅之二:开始使用Ninject(附程序下载)

    摘要 这篇文章介绍怎样将Ninject添加到实际的项目中,使用Ninject框架最基本的功能.首先用一个Hello World例子介绍怎么添加和使用Ninject.然后用一个更复杂的例子,介绍Ninj ...

  6. Ninject之旅之十四:Ninject在ASP.NET Web Form程序上的应用(附程序下载)

    摘要 ASP.NET Web Forms没有像MVC那样的可扩展性,也不可能使它创建UI页面支持没有构造函数的的激活方式.这个Web Forms应用程序的的局限性阻止了它使用构造函数注入模式,但是仍能 ...

  7. Ninject之旅之九:Ninject上下文绑定(附程序下载)

    摘要 既然在插件模型里,每一个服务类型可以被映射到多个实现,绑定方法不用决定要返回哪个实现.因为kernel应该返回所有的实现.然而,上下文绑定是多个绑定场景,在这个场景里,kernel需要根据给定的 ...

  8. Ninject之旅之七:Ninject依赖注入

    摘要 可以使用不同的模式向消费者类注入依赖项,向构造器里注入依赖项是其中一种.有一些遵循的模式用来注册依赖项,同时有一些需要避免的模式,因为他们经常导致不合乎需要的结果.这篇文章讲述那些跟Ninjec ...

  9. Ninject之旅之六:Ninject约定

    摘要 在小的应用系统中一个一个注册一些服务类型不怎么困难.但是,如果是一个实际的有上百个服务的应用程序呢?约定配置允许我们使用约定绑定一组服务,而不用一个一个分别绑定. 要使用约定配置,需要添加Nin ...

随机推荐

  1. Linux 基础笔记

    1    vim /etc/inittab     linux配置文件,这个文件负责设置init初始化程序初始化脚本在哪里;2    passwd    修改密码2.1    passwd xxx   ...

  2. VPython 三维显示 —— hello word

    使用VPython,python(x,y)中已安装VPython模块 from visual import * cylinder(pos=(0,1,0), axis=(0,1,0), radius=0 ...

  3. Javascript中变量提升的问题

    一.函数声明变量提升 函数声明具有变量提升的问题,所以在函数被声明之前就可以访问. //else中的语句相当于将if中的function重写,因此无论flag为何值,返回的方法始终为重写后的方法. / ...

  4. office project 激活

    office project 2010激活方式: 图上中,箭头位置,可能没激活,需要软件激活 ( mini-KMS Activator(Office2010激活工具) V1.2 绿色版 ) 激活方式如 ...

  5. 公司系统的license授权

    在我的电脑的myeclipse的whnaproject项目下.可直接编译生成license.xml中所需要的授权码. 引用了spring-license-0.02.jar. package com.w ...

  6. 64位Windows2008下插入Oracle中文乱码问题解决

    最近迁移一个.net应用系统从32位Windows2003升级到64位的Windows2008中,发现所有涉及中文的内容插入到Oracle数据库中都变为?,最开始以为是2008系统的字符集与Oracl ...

  7. (Xaml) Type 'DeviceA' is not defined.

    修改了一些Xaml, 始终提示 Compiler error(s) encountered processing expression "deviceA.B".Type 'Devi ...

  8. html_博客博主

    csdn: 工匠若水 http://blog.csdn.net/yanbober yunama: IT蓝豹:http://www.itlanbao.com/: http://ask.dcloud.ne ...

  9. sql 中 left join 的使用

    left join .是以左表为基础,查询右表的值.如果在右表中没用没有数据,则为NULL. 这里有三张表. 线路bs_line:id,name(id主键) 线路段bs_seg:id,l_id,nam ...

  10. Knock: 使用压电传感器来检测敲击

    原文链接:https://www.arduino.cc/en/Tutorial/Knock 敲击检测 本教程介绍如何使用压电传感器检测振动,比如敲门.桌子或其他固体表面. 压电传感器是一种能够在振动. ...