Unity Application Block (Unity) 是微軟 Patterns & Practices 團隊開發的一個輕量級、可擴展的依賴注入容器,有助於構建松散耦合的系統。它支持構造子注入 (Constructor Injection) , 屬性/設值方法注入 (Property/Setter Injection) 和方法調用注入 (Method Call Injection) 。Patterns & Practices 團隊於前些天(4月4日)發布了Unity的第一個正式版本(Unity 1.0)。
准備工作
先看一些接口和類,下面會用到:
public interface IPlayer
{
void Play();
}
public class Mp3Player : IPlayer
{
public void Play()
{
Console.WriteLine("Playing Mp3");
}
}
public class CDPlayer : IPlayer
{
public void Play()
{
Console.WriteLine("Playing CD");
}
}
public class DVDPlayer : IPlayer
{
public void Play()
{
Console.WriteLine("Playing DVD");
}
}
以一個簡單的例子開始
//創建Unity容器
IUnityContainer container = new UnityContainer();
//注冊類型映射
container.RegisterType<IPlayer, Mp3Player>();
//獲取對象實例,由於上一步已在容器中將IPlayer接口映射為Mp3Player類,
//所以這裡會自動裝載Mp3Player類,創建該類的實例
IPlayer player = container.Resolve<IPlayer>();
//調用實例方法
player.Play();
輸出為:
注冊映射
在Unity中可以通過兩種方式來為Container注冊映射:
1. RegisterType:
在容器中注冊“接口”( 包括 Interface 和 Base Class ) 到“具體類”的映射。在需要的時候 ( 如上例中container.Resolve<IPlayer>() ) ,容器會根據指定的接口創建它所映射的具體類的實例。
如:
//注冊IPlay接口映射為Mp3Player類
container.RegisterType<IPlayer,Mp3Player>();
//注冊BasePlay基類映射為Mp3Player類
container.RegisterType<BasePlayer,Mp3Player>();
一個接口可能會映射多個具體類,可以為每個映射指定Name,如:
container.RegisterType<IPlayer,Mp3Player>("myMp3Player1");
RegisterType有以下幾個重載方法:
RegisterType<TFrom, TTo>( )
RegisterType<TFrom, TTo>(LifetimeManager lifetime)
RegisterType<TFrom, TTo>(String name)
RegisterType<TFrom, TTo>(String name, LifetimeManager lifetime)
RegisterType<T>(LifetimeManager lifetime)
RegisterType<T>(String name, LifetimeManager lifetime)
RegisterType(Type from, Type to)
RegisterType(Type from, Type to, String name)
RegisterType(Type from, Type to, LifetimeManager lifetime)
RegisterType(Type from, Type to, String name, LifetimeManager lifetime)
RegisterType(Type t, LifetimeManager lifetime)
RegisterType(Type t, String name, LifetimeManager lifetime)
關於LifttimeManager可以參考下面文章:
Unity Application Block 1.0系列(7): Lifetime Managers
2. RegisterInstance:
在容器中注冊“接口”( 包括 Interface 和 Base Class ) 到“實例”的映射。
Mp3Player mp3Player = new Mp3Player();
//注冊IPlayer接口映射為mp3Player實例
container.RegisterInstance<IPlayer>(mp3Player);
同樣的也可以為該映射指定Name,如:
container.RegisterInstance<IPlayer>("myMp3Player",mp3Player);
RegisterInstance有以下幾個重載方法:
RegisterInstance<TInterface>(TInterface instance)
RegisterInstance<TInterface>(TInterface instance, LifetimeManager lifetime)
RegisterInstance<TInterface>(String name, TInterface instance)
RegisterInstance<TInterface>(String name, TInterface instance, LifetimeManager lifetime)
RegisterInstance(Type t, Object instance)
RegisterInstance(Type t, Object instance, LifetimeManager lifetime)
RegisterInstance(Type t, String name, Object instance)
RegisterInstance(Type t, String name, Object instance, LifetimeManager lifetime)
獲取對象實例
在Unity中通過Resolve方法獲取映射的類的對象實例。
回到本文開始的那個例子,看這行代碼:
IPlayerplayer=container.Resolve<IPlayer>();
這裡就是通過Resolve方法獲取IPlayer接口所映射的Mp3Player的實例。
繼續看下面的代碼:
IUnityContainer container = new UnityContainer();
container.RegisterType<IPlayer, Mp3Player>();
container.RegisterType<IPlayer, CDPlayer>();
IPlayer player = container.Resolve<IPlayer>();
player.Play();
這裡為IPlayer接口注冊兩個映射類:Mp3Player和CDPlayer。通過Resolve方法獲取到的這個player對象是Mp3Player還是CDPlayer的實例呢?
看看運行結果:
Unity中當遇到一個接口映射到多個具體類情況時,Resolve時候使用最後注冊的映射。
所以上面輸出結果為 "Playing CD" 就不足為怪。
再繼續看一個例子:
IUnityContainer container = new UnityContainer();
container.RegisterType<IPlayer, Mp3Player>();
//注冊時指定Name為"myCDPlayer"
container.RegisterType<IPlayer, CDPlayer>("myCDPlayer");
IPlayer player1 = container.Resolve<IPlayer>("myCDPlayer");
IPlayer player2 = container.Resolve<IPlayer>();
player1.Play();
player2.Play();
這時候player1和player2各表示哪個類的實例呢?
看看輸出結果:
可以看出Resolve方法可以通過指定Name( 這個Name值對應著RegisterType時指定的Name值 ),通過這種方式可以獲取到指定的對象實例。所以當一個接口映射到多個具體類時,還是很有必要為這些映射指定Name。
注意:Name值為大小寫敏感的。
上面的player2對象由於Resolve時候沒有指定Name,則使用默認的映射關系(RegisterType或RegisterInstance時如果不指定Name,則這個映射就為該接口的默認映射,如果有多個這樣的默認映射,還是按照那個規則,以最後一個有效)。
繼續再來看一個例子:
IUnityContainer container = new UnityContainer();
container.RegisterType<IPlayer, Mp3Player>("myCDPlayer");
container.RegisterType<IPlayer, CDPlayer>("myCDPlayer");
IPlayer player = container.Resolve<IPlayer>("myCDPlayer");
player.Play();
輸出為:
可以看出這個還是滿足剛才說的那個原則:
“Unity中當遇到一個接口映射到多個具體類情況時,Resolve時候使用最後注冊的映射”
看一下Resolve的重載方法:
Resolve<T>( )
Resolve<T>(string name)
Resolve(Type t)
Resolve(Type t, string name)
獲取所有對象實例
可以通過ResolveAll方法來獲取指定接口的所有對象實例:
IUnityContainer container = new UnityContainer();
輸出結果為:
container.RegisterType<IPlayer, Mp3Player>("myMP3Player");
container.RegisterType<IPlayer, CDPlayer>("myCDPlayer");
container.RegisterType<IPlayer, DVDPlayer>();
IEnumerable<IPlayer> players = container.ResolveAll<IPlayer>();
foreach (IPlayer player in players)
{
player.Play();
}
例子中為IPlayer接口映射了三個具體類,但是通過輸出的結果發現,只有輸出CD和MP3的,也就是players對象就只包含CDPlayer和MP3Player的實例,為什麼會出現這種情況?
Untiy中ResolveAll方法只是返回注冊映射時有指定Name的所有類的實例。
所以不難看出上例中雖然有為IPlayer接口映射了三個具體類,但是由於RegisterType<IPlayer, DVDPlayer>()時沒指定Name,所以通過ResolveAll方法獲取不到該實例。
ResolveAll有以下兩個重載方法:
ResolveAll<T>( )
ResolveAll(Type t)