Uinty框架学习记录(二)--依赖注入
依赖项注入 (DI) 是一项用于生成松散耦合的应用程序的主要技术。依赖项注入描述了设计应用程序的过程,这样,应用程序不在设计时在应用程序中指定具体依赖项和创建代码中所需的对象,而是在运行时决定它需要的对象,生成这些对象并将其注入到应用程序中。这也是unity的主要功能之一。
定义依赖的方式有三种:使用配置文件,在程序中使用代码动态注册,使用特性。
这三种的定义方式的优缺点在microsoft的官方文档中有写到:
总结一下就是:
1.使用配置文件很灵活,可以轻松的更改注册和依赖映射,但是在类的源代码中看不到依赖关系,可读性不好。而且写配置文件很麻烦(个人体会)。
2.使用动态注册,也很灵活,但是需要一个程序启动的启动文件,并在启动文件中配置。缺点也是在源代码中看不到依赖关系,可读性差。
3.使用特性的话是最方便,而且直接在源代码中可以看到依赖关系,可读性好。但是,要修改依赖关系的要在源代码中修改,灵活性比较差。
下面再来看看三种依赖注入的方式。
构造器注入
先在上一篇的项目中添加类:
public interface IProgrammer { void Introduce(); } public class CSharpProgrammer : IProgrammer { public void Introduce() { Console.WriteLine("我是一个C#程序员"); } } public class JavaProgrammer : IProgrammer { public void Introduce() { Console.WriteLine("我是一个Java程序员"); } } public interface ICompany { void Display(); void SetProgrammer(IProgrammer programmer); } public class Company:ICompany { public Company() { } public Company(IProgrammer programmer) { this.Programmer = programmer; } public IProgrammer Programmer { get; set; } public void SetProgrammer([Dependency("net")] IProgrammer programmer) { this.Programmer = programmer; } public void Display() { Console.WriteLine("这是我们公司的程序员"); Programmer.Introduce(); } }
Company有默认的构造函数和一个带参数的构造函数。
使用配置文件注入:
<unity> <aliases> <add alias="CSharpProgrammer" type="Model.CSharpProgrammer,Model"/> <add alias="JavaProgrammer" type="Model.JavaProgrammer,Model"/> <add alias="IProgrammer" type="Model.IProgrammer,Model"/> <add alias="ICompany" type="Model.ICompany,Model"/> <add alias="Company" type="Model.Company,Model"/> </aliases> <container name="MyContainer"> <register type="IProgrammer" mapTo="CSharpProgrammer" name="net"></register> <register type="IProgrammer" mapTo="JavaProgrammer" name="java"></register> <register type="ICompany" mapTo="Company" name="company"> <constructor> <param name="programmer" type="IProgrammer"> <dependency type="IProgrammer" name="net"/> </param> </constructor> </register> </container> </unity>
调用代码:
IUnityContainer container = new UnityContainer(); ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); fileMap.ExeConfigFilename = "Unity.config"; Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection("unity"); container.LoadConfiguration(section, "MyContainer"); ICompany company = container.Resolve<ICompany>("company"); company.Display(); Console.ReadKey();
以后要修改注入只用在配置文件中修改就可以了,都不用改源代码,真的很灵活。
使用动态注册:
IUnityContainer container = new UnityContainer(); container.RegisterType<IProgrammer, CSharpProgrammer>("net"); container.RegisterType<IProgrammer, JavaProgrammer>("java"); container.RegisterType<ICompany, Company>("company",new InjectionConstructor(new ResolvedParameter<IProgrammer>("java"))); ICompany company = container.Resolve<ICompany>("company"); company.Display(); Console.ReadKey(); }
ResolvedParameter类可以从容器中获取一个指定名称的组件,使用泛型表示这个组件类型。
使用特性注册:
public Company() { }
[InjectionConstructor] public Company([Dependency("java")]IProgrammer programmer) { this.Programmer = programmer; }
当有多个构造函数时,Unity 将选择最复杂的构造函数(通常具有最大数量的参数),通过容器解析每个参数的类型,然后使用解析的值创建目标类型的新实例。所以当有多个构造函数,又没有用InjectionConstructor指定是使用哪个构造函数初始化时,会出现一些意外的情况。
属性注入:
使用配置配置文件
<unity> <aliases> <add alias="CSharpProgrammer" type="Model.CSharpProgrammer,Model"/> <add alias="JavaProgrammer" type="Model.JavaProgrammer,Model"/> <add alias="IProgrammer" type="Model.IProgrammer,Model"/> <add alias="ICompany" type="Model.ICompany,Model"/> <add alias="Company" type="Model.Company,Model"/> </aliases> <container name="MyContainer"> <register type="IProgrammer" mapTo="CSharpProgrammer" name="net"></register> <register type="IProgrammer" mapTo="JavaProgrammer" name="java"></register> <register type="ICompany" mapTo="Company" name="company"> <property name="Programmer" dependencyName="java" dependencyType="IProgrammer" /> </register> </container> </unity>
使用动态注册:
IUnityContainer container = new UnityContainer(); container.RegisterType<IProgrammer, CSharpProgrammer>("net"); container.RegisterType<IProgrammer, JavaProgrammer>("java"); container.RegisterType<ICompany, Company>("company",new InjectionProperty("Programmer",new ResolvedParameter<IProgrammer>("net"))); ICompany company = container.Resolve<ICompany>("company"); company.Display(); Console.ReadKey();
使用特性:
[Dependency("java")] public IProgrammer Programmer { get; set; }
方法注入
和构造器和属性注入相比,方法注入用的就不太多了。但它在两种特定情况下非常有用。首先,构造函数注入仅在您实例化对象的新实例(当执行构造函数)时适用,而方法调用注入适用于现有的对象实例。其次,当属性 setter 注入也适用于现有实例时,它需要公开公共属性。使用方法调用注入意味着您不需要公开公共属性,便能够将值注入到已解析类型的现有实例。
配置文件注入:
<unity> <aliases> <add alias="CSharpProgrammer" type="Model.CSharpProgrammer,Model"/> <add alias="JavaProgrammer" type="Model.JavaProgrammer,Model"/> <add alias="IProgrammer" type="Model.IProgrammer,Model"/> <add alias="ICompany" type="Model.ICompany,Model"/> <add alias="Company" type="Model.Company,Model"/> </aliases> <container name="MyContainer"> <register type="IProgrammer" mapTo="CSharpProgrammer" name="net"></register> <register type="IProgrammer" mapTo="JavaProgrammer" name="java"></register> <register type="ICompany" mapTo="Company" name="company"> <method name="SetProgrammer"> <param name="programmer" dependencyName="net" dependencyType="IProgrammer" /> </method> </register> </container> </unity>
方法注入的方法不用我们显示调用。unity在对象出事话后自动调用。
动态注入:
IUnityContainer container = new UnityContainer(); container.RegisterType<IProgrammer, CSharpProgrammer>("net"); container.RegisterType<IProgrammer, JavaProgrammer>("java"); container.RegisterType<ICompany, Company>("company",new InjectionMethod("SetProgrammer",new ResolvedParameter<IProgrammer>("java"))); ICompany company = container.Resolve<ICompany>("company"); company.Display();
使用特性:
[InjectionMethod] public void SetProgrammer([Dependency("net")]IProgrammer programmer) { Console.WriteLine("SetProgrammer invoked"); this.Programmer = programmer; }
最后来总结一下:
三种注入,构造器注入和方法注入用的最多,方法注入用的较少,但是可以在不暴露属性的情况下对已生成的实例进行注入。定义依赖注入的三种方式,配置文件比较灵活,但是写起来很繁琐。使用特性很简单方便,但是不够灵活。使用动态注入很灵活,写起来也比较简洁,我觉得是比较好的定义方式。