IOC详解和Unity基础使用介绍
IOC全称是Inversion Of Control,意为控制反转,可什么是控制反转呢?
按我现在的理解,把上端依赖的项从细节转换为抽象,并把细节转移到第三方,这个就叫控制反转。
怎么理解呢?最简单的:我们有一个接口Ianimal,就像这样:
1
2
3
4
|
public interface iAnimal
{
void talk();
}
|
然后我们现在用另一个类去实现该接口:
1
2
3
4
5
6
7
|
public class dog : iAnimal
{
public void talk()
{
Console.Write( "小狗说:汪汪汪!" );
}
}
|
然后呢,往常来讲,我们应该是这样调用的:
1
2
3
|
iAnimal dog = new dog();
dog.talk(); Console.ReadKey(); |
运行结果应该是这样的:
到目前为止都是正确的,相信各位小伙伴一般也是如此。
但请注意,我们在调用时是直接用dog类型new出来的,这种做法其实就已经依赖于细节了。
那么如何将这里依赖的细节修改为抽象呢?很简单,我们只需要一个工厂类帮助我们生成实际的类即可。
但是有些小伙伴可能想,工厂内也有细节啊,难道写个工厂就是IOC了么?
当然不是,我们还将细节交给配置文件(通过反射),这样在功能变动时,无需修改原代码,只需要修改配置文件即可。
工厂看上去是这样的:
1
2
3
4
5
6
7
8
9
10
|
public class SimpleFactory
{
public static iAnimal CreateAnimal()
{
string classModule = ConfigurationManager.AppSettings[ "PhoneType" ];
Assembly assembly = Assembly.Load(classModule.Split( ',' )[1]);
Type type = assembly.GetType(classModule.Split( ',' )[0]);
return (iAnimal)Activator.CreateInstance(type);
}
}
|
而我们的配置文件自然也要写点东西了:
1
2
3
|
<appSettings> <add key= "AnimalType" value= "IOCandDI.dog,IOCandDI" />
</appSettings> |
value内,逗号前是类的全限定名(命名空间+类名),后面是命名空间。
不会操作配置文件的小伙伴们,先添加这个引用:
然后引入System.Configuration命名空间就可以操作啦。
至于反射,直接引入System.Reflection 命名空间即可操作。反射的原理也很简单,在这里就不再赘述。
修改过后的调用和以前的调用对比:
1
2
3
4
5
6
|
iAnimal dog = new dog();
dog.talk(); iAnimal obj = SimpleFactory.CreateAnimal(); obj.talk(); Console.ReadKey(); |
结果是一样的:
这个时候,如果再多一种动物,只需要添加一个继承iAnimal接口的类,并且修改配置文件即可。
IOC带给我们的便利正是如此。
微软也推出了一款IOC的插件,叫做Unity,你在NuGet程序包管理中很轻松就能找到它:
首先是下载与安装:
然后在页面引入Microsoft.Practices.Unity就可以操作了,基本流程如下:
1
2
3
4
|
IUnityContainer container = new UnityContainer(); //声明容器
container.RegisterType<iAnimal, cat>(); //注册类型
iAnimal obj = container.Resolve<cat>(); //完成实例
obj.talk(); |
运行效果:
了解了Unity的基础用法,我们在来看下如何利用Unity做依赖注入(DI——dependency injection)。
说到DI,就一定要知道DI的三种方式:属性注入、构造注入、方法注入。我这里直接改造了cat类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class cat : iAnimal
{
[Dependency]
public iColor color { get ; set ; }
public iEat eat { get ; set ; }
public iRun run { get ; set ; }
[InjectionConstructor]
public cat(iEat ieat)
{
eat = ieat;
}
public void talk()
{
Console.WriteLine( "小猫说:喵喵喵!" );
}
[InjectionMethod]
public void Happy(iRun irun)
{
run = irun;
}
}
|
大家一定发现了我分别在属性、构造、方法头上加了特性。
实际上,Dependency、InjectionConstructor、InjectionMethod就是Unity中DI操作的标识符,它们分别对应属性注入、构造注入、方法注入。
那么,这些特性是如何帮助程序完成DI的呢?看调用代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
IUnityContainer container = new UnityContainer();
container.RegisterType<iAnimal, cat>(); container.RegisterType<iColor, Color>(); container.RegisterType<iEat, Eat>(); container.RegisterType<iRun, Run>(); iAnimal obj = container.Resolve<cat>(); obj.talk(); cat myCat = (cat)obj; Console.WriteLine( "myCat.color是空的么?{0}" , myCat.color == null );
Console.WriteLine( "myCat.eat是空的么?{0}" , myCat.eat == null );
Console.WriteLine( "myCat.run是空的么?{0}" , myCat.run == null );
Console.ReadKey(); |
在Unity容器内类型注册时,需注册cat类内3个参数的类型,用来对应3种注入方式。
运行结果如下:
3种注入方式都成功了~添加了特性的属性(或方法)在Unity容器执行实例时会自动寻找上面3个特性(的位置)并匹配注册类型(完成注入)。
需要注意的是,构造注入无需声明特性也可生效,Unity会自动寻找参数最多的构造参数进行注入(前提是被注入的参数类型一定要注册)。
Unity IOC通过配置实现类型映射
注意配置中的[]方括号表示的是泛型参数,typeAliases->typeAlias中的type及types->type中的mapTo均需配置类型的完整限定名以及类型所在的程序集,中间以逗号分隔,比如:ConsoleApplication1.DDD.Interface.IRepository`1 表示一个IRepository<T>泛型类型,而ConsoleApplication1表示这个泛型所在的程序集,并不是命名空间哦!
第二种配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
< configSections >
< section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>
</ configSections >
< unity >
< aliases >
< add alias="IRepository`1" type="ConsoleApplication1.DDD.Interface.IRepository`1,ConsoleApplication1" />
< add alias="Post" type="ConsoleApplication1.Entities.Post,ConsoleApplication1" />
< add alias="Category" type="ConsoleApplication1.Entities.Category,ConsoleApplication1" />
< add alias="Author" type="ConsoleApplication1.Entities.Author,ConsoleApplication1" />
< add alias="IClassTest" type="ConsoleApplication1.DDD.Interface.IClassTest,ConsoleApplication1" />
</ aliases >
< container >
< register type="IRepository[Post]" mapTo="ConsoleApplication1.DDD.Infrastructure.Repositories.PostRepository,ConsoleApplication1" />
< register type="IRepository[Category]" mapTo="ConsoleApplication1.DDD.Infrastructure.Repositories.CategoryRepository,ConsoleApplication1" />
< register type="IRepository[Author]" mapTo="ConsoleApplication1.DDD.Infrastructure.Repositories.AuthorRepository,ConsoleApplication1" />
< register type="IClassTest" mapTo="ConsoleApplication1.DDD.Interface.ClassTest,ConsoleApplication1" ></ register >
</ container >
</ unity >
|
代码中使用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
unityContainer = new UnityContainer();
unityContainer.LoadConfiguration(); //如果配置中指定了容器节点的名字name,则需要指定名字
unityContainer.Resolve<IRepository<Post>>(); //解析并实例化一个对象
//以下是采用独立的配置文件(如:unity.config)的使用方法: IUnityContainer container = new UnityContainer();
string configFile = "Unity.config" ;
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = configFile };
//从config文件中读取配置信息
Configuration configuration =ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
//获取指定名称的配置节
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection( "unity" );
//载入名称为FirstClass 的container节点
container.LoadConfiguration(section, "MyContainer" );
|