代码改变世界

IOC注入框架——Unity中的BuildUp与LifetimeManagers

2012-10-27 22:30  C#与.NET探索者  阅读(764)  评论(0编辑  收藏  举报

一、使用BuildUp向已存在的对象进行依赖注入
准备工作:编写下面的几个类


public class Owner
{
    public string Name
    {
        get
        {
            return "Inrie";
        }
    }

    public int Age
    {
        get
        {
            return 24;
        }
    }
}

public abstract class Player
{
    public abstract void Play();

    public abstract string Name { get; }

    [Dependency]
    public Owner Owner { get; set; }
}

public class Mp3Player : Player
{
    public override void Play()
    {
        Console.WriteLine("Playing Mp3");
    }

    public override string Name
    {
        get
        {
            return "Mp3 Player";
        }
    }
}

public class CDPlayer : Player
{
    public override void Play()
    {
        Console.WriteLine("Playing CD");
    }

    public override string Name
    {
        get
        {
            return "CD Player";
        }
    }
}

public class DVDPlayer : Player
{
    public override void Play()
    {
        Console.WriteLine("Playing DVD");
    }

    public override string Name
    {
        get
        {
            return "DVD Player";
        }
    }
}
public class TempBuildUp
{
public TempBuildUp()
{
}
}
public class CoreLib
{
    public Player CreatePlayer()
    {
        return new Mp3Player();
    }
}

按照我们上篇文章说的依赖注入,我们看到在MP3Player这个类中的Name属性依赖于Owner类,因此在编写代码的时候我们需要使用UnityContainer进行依赖注入。
如:
    IUnityContainer container = new UnityContainer();
    container.RegisterType<Player, Mp3Player>();
    Player player = container.Resolve<Player>();
    Response.Write(player.Owner.Name);
   
但是如果我把代码改为如下方式:
    Player player = new Mp3Player();
    Response.Write(player.Owner.Name);
代码会抛出异常("未将对象引用设置到对象实例"),这是什么原因呢?
因为,我们没有使用UnityContainer容器实例化Player,如果目标对象并非由容器实例化(非 Resolve 创建),那么依赖注入行为是不会发生的。(车延禄)

除了使用UnitContainer对象的Resolve方法实例化对象,我们还可以使用BuildUp进行操作,BuildUp就是对已存在的对象进行依赖注入。
如:
    IUnityContainer container = new UnityContainer();
    Player player = new Mp3Player();    //未实现依赖的Player对象
    Player p = container.BuildUp<Player>(player);   //对player对象进行依赖注入
    Response.Write(p.Owner.Name);   //执行成功。
   
下面再看一个例子:
通过CoreLib类来创建Player对象,然后显示该Player对象的Owner.Name属性。
现在出现了一个新的问题:Player对象的创建过程在Corelib的CreatePlayer方法中执行的,该方法只是简单地生成MP3Player对象,并没有对它进行依赖注入,即意味着,CoreLib.CreatePlayer()生成的Player对象的Owner引用是Null。
所以如果客户码这样编写就错了:
    CoreLib lib = new CoreLib();
    Player player = lib.CreatePlayer();
    Response.Write(player.Owner.Name);
聪明的朋友一眼能看出来,这里没有使用依赖注入。但由于Player对象的生成在CoreLib中实现,并不在我们客户代码的控制范围之内,因此我们无法使用Resolve在客户代码中通过CoreLib对象创建Player对象实例。
在这种情况下,我们可以使用Container的BuildUp()方法,在CoreLib对象中动态进行依赖注入。
代码如下:
    IUnityContainer container = new UnityContainer();
    CoreLib lib = new CoreLib();    //生成CoreLib对象
    Player p = lib.CreatePlayer(); //通过CoreLib对象生成Player对象,但此时p还是个“半成品”
    Player player = container.BuildUp<Player>(p);   //对“半成品”p进行依赖注入
    Response.Write(player.Owner.Name); //执行成功
   
二、Lifetime Managers 管理对象的生命周期
Lifetime Managers控制怎样存放到对象实例的引用和容器怎样销毁这些实例,也就是说Unity基于具体的Lifetime Manager 类去管理对象的创建和销毁。
目前Unity中提供两个Lifetime Manager类可供我们直接使用,当然你也可以实现自己的Lifetime Manager类。
A. ContainerControlledLifetimeManager
Unity保存一个指向对象实例的引用。通过Unity容器为同一个类型或对象获取对象实例时,每次获取到的都是同一个实例。也就是说实现了对象单例模式。默认情况下,RegisterInstance方法使用该Lifetime Manager。

B. ExternallyControlledLifetimeManager
Unity仅保存一个指向对象实例的弱引用。通过Unity容器为同一个类型或对象获取对象实例时,每次获取到的都是同一个实例,也是单例模式。但是由于当对象创建完之后,容器没有对该对象的强引用,所以就可能出现当其他地方没有去强引用它时候,会被GC回收掉。

1.RegisterType
当用RegisterType注册映射关系时,如果没有指定LifetimeManager,默认是使用一个瞬态的Lifetime Manager。即每次通过Unity容器获取对象的实例时都会重新创建一个该实例,也就是说Unity容器不存在一个到该对象的引用。
如:
IUnityContainer container = new UnityContainer();
container.RegisterType<IPlayer, Mp3Player>();

IPlayer player1 = container.Resolve<IPlayer>();
Console.WriteLine(string.Format("Player1 HashCode: {0}",player1.GetHashCode()));

IPlayer player2 = container.Resolve<IPlayer>();
Console.WriteLine(string.Format("Player2 HashCode: {0}",player2.GetHashCode()));
代码执行结果:


通过输出的player1和player2对象的HashCode值可以看出,player1和player2分别是Mp3Player类的不同实例。
要实现单例模式,通过在RegisterType时为它指定相应的Lifetime Manager可以实现单例模式,从上面对ContainerControlledLifetimeManager和ExternallyControlledLifetimeManager的介绍可以知道,这两个Lifetime Manager都可以支持单例模式。
如:
IUnityContainer container = new UnityContainer();
//这里指定使用ContainerControlledLifetimeManager对象
container.RegisterType<IPlayer, Mp3Player>(new ContainerControlledLifetimeManager());

IPlayer player1 = container.Resolve<IPlayer>();
Console.WriteLine(string.Format("Player1 HashCode: {0}",player1.GetHashCode()));

IPlayer player2 = container.Resolve<IPlayer>();
Console.WriteLine(string.Format("Player2 HashCode: {0}",player2.GetHashCode()));
执行结果


通过输出结果可以看出,player1和player2对象为Mp3Player类的同一实例,指向同一内存地址。

2. RegisterInstance
当用RegisterInstance注册映射关系时,如果没有指定Lifetime Manager,默认是使用ContainerControlledLifetimeManager,即支持单例模式。
如:(车延禄)
IUnityContainer container = new UnityContainer();
IPlayer mp3Player = new Mp3Player();
container.RegisterInstance<IPlayer>(mp3Player);

IPlayer player1 = container.Resolve<IPlayer>();
Console.WriteLine(string.Format("Player1 HashCode: {0}", player1.GetHashCode()));

IPlayer player2 = container.Resolve<IPlayer>();
Console.WriteLine(string.Format("Player2 HashCode: {0}", player2.GetHashCode()));
代码执行结:


通过输出结果可以看出,player1和player2对象为Mp3Player类的同一实例,指向同一内存地址。