Unit简单依赖注入
准备工作:还是上节课的几个类:
//抽象类,播放器
public abstract class Player
{
public abstract string Play(); //播放功能
}
//MP3播放器
public class MP3Player : Player
{
public override string Play()
{
return "this is MP3Player";
}
}
//CD播放器
public class CDPlayer : Player
{
public override string Play()
{
return "this is CDPlayer";
}
}
//DVD播放器
public class DVDPlayer : Player
{
public override string Play()
{
return "this is DVDPlayer";
}
}
一、构造子注入(Constructor Injection)
当我们使用Unity的Resove方法动态构造一个对象A的时候,有可能会出现这样的问题:该类的构造函数的形参依赖于另外一个类的对象B(即,形参列表中要接收另外一个对象的实例 A->B)。
这样在动态生成对象A之前必须要先动态生成对象B,然后再把对象B注入到A的构造函数中,生成A对象。这就是我们所说的构造子注入。
如:再加入下面一个类
public class ClassRoom
{
private Player player; //播放器的抽象
public ClassRoom(Player player) //构造函数,接收一个Player类型的对象
{
this.player = player;
}
public string Show() //返回播放器的play()结果
{
if (player != null)
return this.player.Play();
else
return "No Player";
}
}
这个类的构造函数中依赖于Play对象。意味着我们在动态生成ClassRoom对象之前要动态生成Player对象。
客户类:
public partial class TempDefault : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<Player, MP3Player>();
ClassRoom cr = container.Resolve<ClassRoom>();
Response.Write(cr.Show());
}
}
运行结果:
分析:在客户类中ClassRoom cr = container.Resolve<ClassRoom>();动态生成ClassRoom对象,在执行ClassRoom构造函数的时发现ClassRoom对象依赖于Player对象。因此它会在container容器中去检索Player的映射,由于我们在此之前加使用container.RegisterType<Player, MP3Player>();把Player映射到MP3Player类上,所以它会先动态生成MP3Player对象,然后注入到ClassRoom的构造函数中生成ClassRoom对象,所以在这里player.Show()方法中调用的应当是MP3Player对象的Show方法。
如果把container.RegisterType<Player, MP3Player>();注释掉,则依赖注入会失败,产生“未将对象引用设置到对象实例”的异常。
说明:
Unity对于单个构造器的情况,将做自动的依赖注入。
多个构造器的情况下,加有[InjectionConstructor]标签的构造器为依赖注入的构造器,如果没有任何构造器有贴上[InjectionConstructor],则使用参数最多的构造器作为依赖注入的构造器
public class ClassRoom
{
private Player player;
public ClassRoom(Player player)
{
this.player = player;
}
[InjectionConstructor]
public ClassRoom(CDPlayer player)
{
this.player = player;
}
public string Show()
{
if (player != null)
return this.player.Play();
else
return "No Player";
}
}
运行结果:
如果在IUnityContainer容器中注册的是一个对象实例,那在在生成ClassRoom对象的时候就不会再次动态构造形参对象,而是直接用已注册的对象实例来实例化ClassRoom对象。
客户类:
public partial class TempDefault : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
IUnityContainer container = new UnityContainer();
DVDPlayer player = new DVDPlayer();
container.RegisterInstance<Player>(player);
ClassRoom cr = container.Resolve<ClassRoom>();
Response.Write(cr.Show());
}
}
运行结果:
二、属性设置注入(Property Setter Injection)
如果我们动态生成某个对象A的时候,想同时为A的某个属性赋值,而该属性又依赖于另一个对象B(即,该属性的类型是类型B),这时就应当使用我们的属性注入。
如果存在属性注入,那它会先动态生成依赖的对象B,然后再把B依赖注入到对象A的属性中去,返回对象A。
属性注入的注法是在属性的上面加上[Dependency]标签。
如果ClassRoom类变为:
public class ClassRoom
{
private Player player;
public Player Player
{
get
{
return player;
}
set
{
player = value;
}
}
public string Show()
{
if (player != null)
return this.player.Play();
else
return "No Player";
}
}
运行结果:
我们发现并没有使用public Player Player{get;set;}属性初始化成员变量。
如果ClassRoom类变为:
public class ClassRoom
{
private Player player;
[Dependency]
public Player Player
{
get
{
return player;
}
set
{
player = value;
}
}
public string Show()
{
if (player != null)
return this.player.Play();
else
return "No Player";
}
}
运行结果:
如果在IUnityContainer容器中注册的是一个对象实例,那在在生成ClassRoom对象的时候就不会再次动态构造形参对象,而是直接用已注册的对象实例来实例化ClassRoom对象。
三、方法调用注入(Method Call Injection)
如果我们动态生成某个对象A的时候,想同时调用A的某个方法,而该方法的形参又依赖于另一个对象B(即,该方法形参的类型是类型B),这时就应当使用我们的方法调用注入。
如果存在方法调用注入,那它会先动态生成依赖的对象B,然后再把B依赖注入到对象A的方法形参中去,返回对象A。
方法调用注入的注法是在方法的上面加上[InjectionMethod]标签。
如果ClassRoom类变为:
public class ClassRoom
{
private Player player;
[InjectionMethod]
public void Init(Player player)
{
this.player = player;
}
public string Show()
{
if (player != null)
return this.player.Play();
else
return "No Player";
}
}(车延禄)
运行结果:
如果在IUnityContainer容器中注册的是一个对象实例,那在在生成ClassRoom对象的时候就不会再次动态构造形参对象,而是直接用已注册的对象实例来实例化ClassRoom对象。
四、防止循环依赖
在介绍 Constructor Injection、Property Injection 和 Method Call Injection 时,都有特别提到不要出现循环引用(Circular References),因为出现这种问题后很难去检测到。最好的解决方法是写代码时候尽量避免出现这种情况。
1.避免通过Constructor Injection生成的对象在构造器的参数中互相引用,以下是错误用法:
public class Class1
{
public Class1(Class2 test2)
{
..
}
}
public class Class2
{
public Class2(Class1 test1)
{
}
}
2. 避免通过Constructor Injection生成的对象作为自身构造器的参数,以下是错误用法:
public class Class1
{
public Class1(Class1 test1)
{ }
}
3. 避免通过method call injection生成的对象互相引用,以下是错误用法:
public class Class1
{
[InjectionMethod]
public void Method1()
{
Method2();
}
[InjectionMethod]
public void Method2()
{
Method1();
}
}
4.避免通过property(setter) injection生成的对象互相引用,以下是错误用法:
public class Class1
{
[Dependency]
public string Propert1
{
get
{
return Propert2;
}
}
[Dependency]
public string Propert2
{
get
{
return Propert1;
}
}
}
(车延禄)