DI原理其实很简单, 我们设计的大类要依赖于小类的接口或基类, 而不是一个具体的小类, 同时不要在大类中创建小类实例, 而是通过大类的构造子或property将小类的实例传进来即可.
目前.Net领域有不少DI容器框架, (这里是DI容器框架列表), 这些框架主要有两个功能, (1)帮助完成依赖的注册与依赖实例的创建, 即绑定小类接口和某个具体小类, 绑定的地方不是在大类中, 所以实现了解耦. (2)更高级的功能是, DI 容器框架可以帮助我们创建大类实例, 就像是一个Factory工厂方法一样, 当然大类所依赖的小类, 也不同我们手动创建, 容器自动按照我们定义的小类绑定关系来创建小类实例.
Ninject是一个新的DI框架, 它的特点是:
(1)不需要使用Xml来定义绑定关系, 使用一个自定义的Module类(基类为StandardModule)来定义绑定关系;
(2)不仅仅支持constructor注入, 而且支持property注册;
(3)对于constrctor注入, 构造子还可以包含其他类型参数, 不仅仅是所依赖的类型参数.
(4)对于非constructor注入, 不仅仅支持无参构造子, 而且支持带参构造子.
(5)支持基于类型的条件绑定, 可以使用Bind().To().ForMembersOf();
(6)部分支持基于variable的Context的条件绑定, 示例代码中对此有说明.(Ninject支持另一种条件绑定, 但它需要在大类的代码加上Parameter attribute或Method attribute, 增加了大类和小类的耦合度, 所以我不喜欢, 好在基于context variable的绑定基本够用了)
(7)对于利用DI容器创建的对象, 可以在定义绑定时, 指定对象的生命周期, 比如Singleton以及SingleCall的生命周期模式.
下面是一个使用Ninject的简单示例, 需要添加Ninject.core和Ninject.condition两个assembly.
主程序类和自定义的Module类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Ninject.Core;
using Ninject.Conditions;
using Ninject.Core.Behavior;
using Ninject.Core.Parameters;
using Ninject.Core.Activation;
namespace NinjectDemo
{
class Program
{
static void Main(string[] args)
{
#region DI: Old way
IEditor editor = new NotePad();
var csProgrammer = new WindowsProgrammer(editor);
csProgrammer.WriteCode();
#endregion
#region DI container
#region Initialization for using Ninject
var module = new ProgrammerModule();
IKernel kernel = new StandardKernel(module);
#endregion
#region Simple inject
var vbProgrammer = kernel.Get<WindowsProgrammer>();
vbProgrammer.WriteCode();
//Get correct concrete class according to binding condition
var linuxProgrammer = kernel.Get<LinuxProgrammer>();
linuxProgrammer.WriteCode();
#endregion
#region Including other parameters rather than inject interface
Dictionary<string, object> param = new Dictionary<string, object>();
//there is another parameter named firstName in constructor
param.Add("firstName", "Harry-Mac OS");
var macProgrammer = kernel.Get<MacProgrammer>(With.Parameters.ConstructorArguments(param));
macProgrammer.WriteCode();
#endregion
#region Control binding by context variable
IParameterCollection pc = With.Parameters.ContextVariable("OS", "Mac").ConstructorArguments(param);
var programmer = kernel.Get<Programmer>(pc);
macProgrammer.WriteCode();
#endregion
#endregion DI container
Console.WriteLine("Enter any key to exit...");
Console.ReadKey();
}
}
class ProgrammerModule : StandardModule
{
/// <summary>
/// 注册接口和对象的绑定
/// </summary>
public override void Load()
{
//This is a default bind for IEditor inteface
Bind<IEditor>().To<NotePad>();
//Bind<IEditor>().To<NotePad>().Always();// same to the above line
//This is a condtional bind for IEditor injection
Bind<IEditor>().To<Vi>().ForMembersOf<LinuxProgrammer>();
//or the following code
//Bind<IEditor>().To<Vi>().OnlyIf(c => c.Member.DeclaringType == typeof(LinuxProgrammer));
//or the following code
//Bind<IEditor>().To<Vi>().OnlyIf(delegate(IContext c) { return c.Member.DeclaringType == typeof(LinuxProgrammer); });
Bind<IEditor>().To<Vi>().ForMembersOf<MacProgrammer>();
//This is another kind of conditional bind.
//我们获取对象要用kernel.Get<T1>()方法, 绑定用Bind<T2>()方法,
// 这种context绑定仅仅适合于T1和T2同一类型或基于同一类型
Bind<Programmer>().To<MacProgrammer>().Only(When.Context.Variable("OS") == "Mac");
}
/// <summary>
/// Load2 方法在本项目中其实没用, 只是为了列出Ninject在绑定时可指定对象的生命周期
/// </summary>
public void Load2()
{
//每次调用kernel.Get<>(), 总是返回一个新的对象, 如果Remoting中讲的SingleCall对象
Bind<IEditor>().To<NotePad>();
//Bind<IEditor>().To<NotePad>().Using<TransientBehavior>(); //same as the above
//获取的对象是Singleton对象, 即使是不同线程, 获得的对象总是同一个
Bind<IEditor>().To<NotePad>().Using<SingletonBehavior>();
//One instance of the type will be created per web request, and will be destroyed when the request ends.
Bind<IEditor>().To<NotePad>().Using<OnePerRequestBehavior>();
//在同一个线程中, 每次调用kernel.Get<>()获取的对象总是同一个
Bind<IEditor>().To<NotePad>().Using<OnePerThreadBehavior>();
}
}
}
下面是IEditor接口和实现类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NinjectDemo
{
interface IEditor
{
string Code();
}
public class Vi : IEditor
{
public string Code()
{
return "This is Vi editor";
}
}
public class NotePad : IEditor
{
public string Code()
{
return "This is NotePad editor";
}
}
}
下面是Programmer基类和派生类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#region Ninject namespace
using Ninject.Conditions;
using Ninject.Core.Behavior;
using Ninject.Core;
#endregion
namespace NinjectDemo
{
abstract class Programmer
{
IEditor m_Editor;
public Programmer(IEditor editor)
{
this.m_Editor = editor;
}
public virtual void WriteCode()
{
Console.WriteLine(m_Editor.Code());
}
}
class WindowsProgrammer:Programmer
{
public WindowsProgrammer(IEditor editor)
: base(editor)
{
}
}
class MacProgrammer : Programmer
{
string m_FirstName;
[Inject]
public MacProgrammer(IEditor editor, string firstName)
: base(editor)
{
m_FirstName = firstName;
}
public override void WriteCode()
{
Console.Write( m_FirstName+" , ");
base.WriteCode();
}
}
class LinuxProgrammer : Programmer
{
[Inject]
public LinuxProgrammer(IEditor editor)
: base(editor)
{
}
}
}