Ninject之旅之十一:Ninject动态工厂(附程序下载)
摘要
如果我们已经知道了一个类所有的依赖项,在我们只需要依赖项的一个实例的场景中,在类的构造函数中引入一系列的依赖项是容易的。但是有些情况,我们需要在一个类里创建依赖项的多个实例,这时候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动态工厂默认的行为。然而,通过创建自定义实例提供者,默认行为可以被重写。后面的文章将要介绍这个。
对动态工厂注册基于约定的绑定和常规的约定绑定稍微有点不同。不同在于,一旦我们选择了程序集,我们应该选择服务类型而不是组件,然后绑定他们到工厂。下面描述怎样实现这两个步骤。
- 选择服务类型
使用下面的方法选择一个抽象类或接口:
- 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动态工厂(附程序下载)的更多相关文章
- Ninject之旅之十二:Ninject在Windows Form程序上的应用(附程序下载)
摘要: 下面的几篇文章介绍如何使用Ninject创建不同类型的应用系统.包括: Windows Form应用系统 ASP.NET MVC应用系统 ASP.NET Web Form应用系统 尽管对于不同 ...
- Ninject之旅之五:Ninject XML配置
摘要 使用XML配置,需要添加Ninject XML扩展的引用.下一步是添加一个或多个包含类型注册的XML文件.记得这些文件应该跟应用程序一起发布.因此不要忘记将XML文件的属性设置成“Copy if ...
- Ninject之旅之十三:Ninject在ASP.NET MVC程序上的应用(附程序下载)
摘要: 在Windows客户端程序(WPF和Windows Forms)中使用Ninject和在控制台应用程序中使用Ninject没什么不同.在这些应用程序里我们不需要某些配置用来安装Ninject, ...
- Ninject之旅之八:Ninject插件模型(附程序下载)
摘要 在前面的章节中,我们看了在单一的绑定条件下Ninject能够处理依赖类型,就是说,每个服务类型只绑定到单一的实现类型.然而,有些情况下我们需要绑定一个抽象服务类型到多个实现,这叫多个绑定.多个绑 ...
- Ninject之旅之二:开始使用Ninject(附程序下载)
摘要 这篇文章介绍怎样将Ninject添加到实际的项目中,使用Ninject框架最基本的功能.首先用一个Hello World例子介绍怎么添加和使用Ninject.然后用一个更复杂的例子,介绍Ninj ...
- Ninject之旅之十四:Ninject在ASP.NET Web Form程序上的应用(附程序下载)
摘要 ASP.NET Web Forms没有像MVC那样的可扩展性,也不可能使它创建UI页面支持没有构造函数的的激活方式.这个Web Forms应用程序的的局限性阻止了它使用构造函数注入模式,但是仍能 ...
- Ninject之旅之九:Ninject上下文绑定(附程序下载)
摘要 既然在插件模型里,每一个服务类型可以被映射到多个实现,绑定方法不用决定要返回哪个实现.因为kernel应该返回所有的实现.然而,上下文绑定是多个绑定场景,在这个场景里,kernel需要根据给定的 ...
- Ninject之旅之七:Ninject依赖注入
摘要 可以使用不同的模式向消费者类注入依赖项,向构造器里注入依赖项是其中一种.有一些遵循的模式用来注册依赖项,同时有一些需要避免的模式,因为他们经常导致不合乎需要的结果.这篇文章讲述那些跟Ninjec ...
- Ninject之旅之六:Ninject约定
摘要 在小的应用系统中一个一个注册一些服务类型不怎么困难.但是,如果是一个实际的有上百个服务的应用程序呢?约定配置允许我们使用约定绑定一组服务,而不用一个一个分别绑定. 要使用约定配置,需要添加Nin ...
随机推荐
- C++ operator 的一种不会的用法
自认为对C++比较熟悉,突然看到一些奇怪的代码(在看网上下载的代码Sockets): class SocketAddress { public: virtual ~SocketAddress() {} ...
- Django模型层Meta内部类详解
Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性.以下对此作一总结: abstract 这个属性是定义当前的模型类是不是一个抽象类.所谓抽象类是不会对应 ...
- vim中添加多行注释和删除多行注释
1.多行注释: a. 按下Ctrl + v,进入列模式; b. 在行首选择需要注释的行; c. 按下"I",进入插入模式: d. 然后输入注释符("//&q ...
- CommittableTransaction和TransactionScope
创建可提交事务 下面的示例创建一个新的 CommittableTransaction 并提交它. //Create a committable transaction tx = new Committ ...
- Mac系统终端命令行不执行命令 总出现command not found解决方法
配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的 保存命令是: source .bas ...
- Java实现多线程的三种方式
Java多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.前两种方式启动的线程没有返回值 ...
- Head First 设计模式之观察者模式(Observer Pattern)
前言: 这一节开始学习观察者模式,开始讲之前会先像第一节那样通过一个应用场景来引入该模式.具体场景为:气象站提供了一个WeatherData对象,该对象可以追踪获取天气的温度.气压.湿度信息,Weat ...
- ArcGis 字段计算表达式写法注意事项
在ArcGis中,经常用到字段的计算.对于复杂的字段计算,需要写代码来实现,在使用ESRI.ArcGIS.DataManagementTools.CalculateField 类时,Python代码中 ...
- zabbix3.0安装之图形界面显示异常【server】
前面记录过Zabbix3.0的安装过程,遇到一些坑,当时就在博文最后提到过,显示界面只有文字没有样式的问题.今天就解决这个小问题. 首先, 我们的安装是基于nginx作为web服务器的,不是传统的用A ...
- 常见数据结构之JavaScript实现
常见数据结构之JavaScript实现 随着前端技术的不断发展,投入到前端开发的人数也越来越多,招聘的前端职位也越来越火,大有前几年iOS开发那阵热潮.早两年,前端找工作很少问到关于数据结构和算法的, ...