Ninject之旅之十一:Ninject动态工厂(附程序下载)
Posted 丹尼大叔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ninject之旅之十一:Ninject动态工厂(附程序下载)相关的知识,希望对你有一定的参考价值。
摘要
如果我们已经知道了一个类所有的依赖项,在我们只需要依赖项的一个实例的场景中,在类的构造函数中引入一系列的依赖项是容易的。但是有些情况,我们需要在一个类里创建依赖项的多个实例,这时候Ninject注入就不够用了。也有些情况,我们不知道一个消费者可能需要哪个服务,因为他可能在不同的场合下需要不同的服务,而且在创建类的时候实例化所有依赖项也不合理。这样的情况,动态工厂可以帮忙。我们可以设计我们的类让他依赖一个工厂,而不是依赖这个工厂能够创建的对象。然后,我们能够命令工厂去通过命令创建需要的类型和任意需要的数量。下面两个例子解决上面两个问题。Ninject动态工厂创建指定数量的依赖项和创建指定类型的依赖项。
这篇文章只介绍Ninject动态工厂创建指定数量的依赖项,下一篇文章介绍Ninject动态工厂创建指定类型的依赖项。
附:代码下载
例子:形状工厂
在第一个例子中,我们将创建一个图形动态库。它包含一个ShapService类,提供一个AddShapes方法来给指定的ICanvas对象添加指定数量具体的IShape对象:
1 public void AddShapes(int circles, int squares, ICanvas canvas) 2 { 3 for (int i = 0; i < circles; i++) 4 { 5 var circle = new Circle(); 6 canvas.AddShap(circle); 7 } 8 for (int i = 0; i < squares; i++) 9 { 10 var square = new Square(); 11 canvas.AddShap(square); 12 } 13 }
传统的方法是直接在AddShapes方法里创建新的Circle和Square类实例。然而,这个方法我们将ShapService类和具体的Circle和Square类耦合起来,这和DI原则相反。另外,通过参数引入这些依赖项不符合我们的需求,因为那样一个形状只注入一个实例,这样不够。为了解决这个问题,我们应该像下面首先创建一个简单工厂接口:
1 public interface IShapeFactory 2 { 3 ICircle CreateCircle(); 4 ISquare CreateSquare(); 5 }
然后,我们可以引入这个工厂接口作为ShapeService类的依赖项。
1 public class ShapeService 2 { 3 private readonly IShapeFactory _factory; 4 5 public ShapeService(IShapeFactory factory) 6 { 7 this._factory = factory; 8 } 9 10 public void AddShapes(int circles, int squares, ICanvas canvas) 11 { 12 for (int i = 0; i < circles; i++) 13 { 14 var circle = _factory.CreateCircle(); 15 canvas.AddShap(circle); 16 } 17 for (int i = 0; i < squares; i++) 18 { 19 var square = _factory.CreateSquare(); 20 canvas.AddShap(square); 21 } 22 } 23 }
好消息是我们不需要担心怎样实现IShapeFactory。Ninject能够动态地实现它,再注入这个实现的工厂到这个ShapeService类。我们只需要添加下面的代码到我们类型注册部分:
1 Bind<IShapeFactory>().ToFactory(); 2 Bind<ISquare>().To<Square>();
3 Bind<ICircle>().To<Circle>();
为了使用Ninject工厂,我们需要添加Ninject.Extensions.Factory动态库的引用。可以通过NuGet添加,也可以通过从Ninject官方网站上下载。
记住工厂可以有需要的尽可能多的方法,每个方法可以返回任意需要的类型。这些方法可以有任意的名字,有任意数量的参数。唯一的限制是名字和参数类型必须跟具体类名字和构造函数参数的类型一致,但是跟他们的顺序没关系。甚至参数的数量都不需要一致,Ninject将试着解析那些没有通过工厂接口提供的参数。
因此,如果具体Square类是下面这样:
1 public class Square 2 { 3 public Square(Point startPoint, Point endPoint) 4 { ... } 5 }
这个IShapeFactory工厂接口就应该像下面这样:
1 public interface IShapeFactory 2 { 3 ICircle CreateCircle(); 4 ISquare CreateSquare(Point startPoint, Point endPoint); 5 }
或者,CreateSquare方法可能像下面这样:
1 ISquare CreateSquare(Point endPoint, Point startPoint);
这是Ninject动态工厂默认的行为。然而,通过创建自定义实例提供者,默认行为可以被重写。后面的文章将要介绍这个。
对动态工厂注册基于约定的绑定和常规的约定绑定稍微有点不同。不同在于,一旦我们选择了程序集,我们应该选择服务类型而不是组件,然后绑定他们到工厂。下面描述怎样实现这两个步骤。
- 选择服务类型
使用下面的方法选择一个抽象类或接口:
- SelectAllIncludingAbstractClasses(): 这个方法选择所有的类,包括抽象类。
- SelectAllAbstractClasses(): 这个方法只选择抽象类。
- SelectAllInterfaces(): 这个方法选择所有接口。
- SelectAllTypes(): 这个方法选择所有类型(类、接口、结构、共用体和原始类型)
下面的代码绑定选择的程序集下的所有接口到动态工厂:
1 kernel.Bind(x => x 2 .FromAssembliesMatching("factories") 3 .SelectAllInterfaces() 4 .BindToFactory());
2. 定义绑定生成器
使用下面的方法定义合适的绑定生成器:
- BindToFactory: 这个方法注册映射的类型作为动态工厂。
- BindWith: 这个方法使用绑定生成器参数创建绑定。创建一个绑定生成器只是关于实现IBindingGenerator接口的问题
下面的例子绑定当前程序集中所有那些以Factory结尾的接口到动态工厂。
1 kernel.Bind(x => x 2 .FromThisAssembly() 3 .SelectAllInterfaces() 4 .EndingWith("Factory") 5 .BindToFactory());
以上是关于Ninject之旅之十一:Ninject动态工厂(附程序下载)的主要内容,如果未能解决你的问题,请参考以下文章
Ninject之旅之九:Ninject上下文绑定(附程序下载)