Aoite 系列(02) - 超動感的 Ioc 容器
Aoite 是一個適於任何 .Net Framework 4.0+ 項目的快速開發整體解決方案。Aoite.Ioc 是一套解決依賴的最佳實踐。
說明: Aoite 是一套快速開發整體解決方案。它不是只有 ORM 或者 Ioc 之類的。框架的內容還是算有點龐大。我需要一點一點的將文章和教程編寫出來,如果加上將其每一部分和其他框架進行比較更需要花費時間。所以所有的入門篇都會簡單的介紹用法,目的是讓使用人員快速入門。若是您想要更快的了解這套框架,可以從單元測試入手。
趕緊加入 Aoite GitHub 的大家庭吧!!
1. 快速入門
控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則。IoC的概念已經提出了非常多年了。
如果...如果...你對 Ioc 不是很理解的話,我們可以這么理解:

秒懂了?不懂也沒關系,反正我也沒打算在這里長篇大論的講解什么是 IoC。網上有許多關控制反轉、依賴注入的相關文章。我就不在這里誤人子弟了 :)
和其他 Ioc 框架先不做比較。Aoite.Ioc 比較有意思的一點是:提倡的是無配置化模式。
我們還是趕緊通過代碼快速了解 Aoite 的 IoC 模塊。
interface IWelcome
{
string GetHelloText();
}
class DefaultWelcome : IWelcome
{
public string GetHelloText()
{
return "Hello World!";
}
}
class ChineseWelcome : IWelcome
{
public string GetHelloText()
{
return "你好,世界!";
}
}
private static void Demo1()
{
IocContainer container = new IocContainer();
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
}
是的,僅僅只有這樣的代碼,當你調用了 Demo1 方法后,它將會直接輸出“Hello World!”文字。
1.1 它是怎么動感的
Aoite.Ioc 模塊有一套類型映射的策略。它是這樣的一步一步的匹配:
- 判定
IWelcome是否已經手工注冊(container.AddService)。 - 判定上級 Ioc 容器是否已手工注冊
IWelcome。 - 判定是否禁用了自動解析的功能(
IocContainer.DisabledAutoResolving),成立則直接返回 null 值。 - 判定
IWelcome是否定義了DefaultMappingAttribute特性。 - 判定是否為基類或值類型,成立則直接返回 null 值。
- 嘗試觸發
IocContainer.MapResolve事件獲取映射類型。 - 嘗試觸發
ObjectFactory.MapResolve靜態事件獲取映射類型。 - 如果以上條件都找不到映射的類型,將會從當前所有已加載的程序集中滿足以下條件的類型(優先級從上至下):
- namespace.DefaultWelcome
- namespace.Welcome
- namespace.FakeWelcome
- namespace.MockWelcome
- 如果以上的條件無法滿足,將會返回一個 null 值。
所以為了我們可以將代碼改成這樣,代替默認的 DefaultWelcome。
private static void Demo1()
{
IocContainer container = new IocContainer();
container.AddService<IWelcome, ChineseWelcome>(); /* 手工注冊 */
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
}
小技巧:通過 IocContainer.MapResolve 事件或 ObjectFactory.MapResolve 靜態事件,你可以應用到 WCF、Remoting 等場景。
1.2 單例模式
講解單例模式之前,我們先來做一個測試:
private static void Demo1()
{
IocContainer container = new IocContainer();
container.AddService<IWelcome, ChineseWelcome>();
Console.WriteLine(container.GetService<IWelcome>() == container.GetService<IWelcome>());
}
它輸出的是 False。為什么會這樣呢?原因是默認情況下,IocContainer 並不會將類型單例化。因為它無法准確判斷你是要創建一個對象,還是每次調用都創建一個新的對象。
所以,如果要單例,你可以嘗試以下幾種方式:
接口特性,這樣的方式會導致獲取這個接口的所有類型,都采用單例模式。
[SingletonMapping]
interface IWelcome
{
//......
}
類特性,只有映射到這個類型,才會成為單例模式。
[SingletonMapping]
class ChineseWelcome : IWelcome
{
//......
}
注冊約定
container.AddService<IWelcome, ChineseWelcome>(true /* singletonMode */);
1.3 懶加載
有時候,我們需要一個類似 Lazy 的懶加載方式,或者你需要根據不同的后期綁定參數,返回不同的類型。你可以這樣折騰:
IocContainer container = new IocContainer();
container.AddService<IWelcome>(lmps =>
{
if(lmps == null || lmps.Length == 0) return new DefaultWelcome();
return new ChineseWelcome();
});
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
Console.WriteLine(container.GetService<IWelcome>("abc").GetHelloText());
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
Console.WriteLine(container.GetService<IWelcome>(1).GetHelloText());
這樣的話,輸出的內容便是:
Hello World!
你好,世界!
Hello World!
你好,世界!
小技巧:通過 InstanceCreatorCallback 委托,可以很靈活的創建對象。
// 摘要:
// 表示實例創建的委托。
//
// 參數:
// lastMappingArguments:
// 后期綁定的參數列表。
//
// 返回結果:
// 返回一個實例。
public delegate object InstanceCreatorCallback(object[] lastMappingArguments);
2. 進階內容
2.1 Key-Value 的映射
IocContainer 除了支持對類型的支持外,還支持類似配置參數的方式。比如你可以這樣玩:
IocContainer container = new IocContainer();
container.AddValue("a", 1);
container.AddValue("b", 2);
Console.WriteLine(container.GetValue("a"));
Console.WriteLine(container.GetValue("b"));
Console.WriteLine(container.GetValue("c") ?? "<NULL>");
這樣有什么意義嗎?第一個意義是可以依賴倒置某些簡單的配置信息。比如數據庫連接字符串、Redis 的連接地址之類。除此之外,還有其他意義嗎?
答案是:有!
2. 帶參數的構造函數
假設我們新增了一種 Welcome 類型:
class CustomWelcome : IWelcome
{
private string _welcomeText;
public CustomWelcome(string welcomeText)
{
this._welcomeText = welcomeText;
}
public string GetHelloText()
{
return "Oh~" + this._welcomeText;
}
}
那么我們該如何映射呢?搜一雞!還支持多種姿勢!
第一種 后期映射
class CustomWelcome : IWelcome
{
//.....
public CustomWelcome([LastMapping]string welcomeText)
//.....
}
指定了 LastMappingAttribute 表示這個參數允許通過后期綁定來賦值。這個特性還可以裝載在類或接口上,表示這個類型/接口如果用在構造函數的話,都會被當作后期綁定參數。
IocContainer container = new IocContainer();
container.AddService<IWelcome, CustomWelcome>();
Console.WriteLine(container.GetService<IWelcome>("自定義歡迎語。").GetHelloText());
第二種 預配模式 不需要加上 LastMappingAttribute 特性,直接通過 Key-Value 映射(指定目標類型優先,並且若存在上級容器,將會尋找到上級容器)。
IocContainer container = new IocContainer();
container.AddValue("welcomeText", "這是一種鳥語的歡迎語。");
container.AddService<IWelcome, CustomWelcome>();
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
第三種 智能模式 適用於類似以下的業務場景:
public class AccountController : Controller
{
public AccountController(IUserRepository userRepository)
//.....
}
userRepository 參數並不需要指定特性 LastMappingAttribute,甚至無需預配 IUserRepository 接口的映射類型。一氣呵成,渾然天成。
需要說明的是,映射的優先級也是從第一種到最后一種。
2.3 Key-Value 的針對性映射
顯然 2.1 中的方式雖然好用,但有些場景卻不適合,比如說不同類型相同參數名稱的場景。這個時候,就可以采用以下方法:
IocContainer container = new IocContainer();
container.AddValue<CustomWelcome>("welcomeText", "這是一種鳥語的歡迎語。");
//- 或 container.AddValue<IWelcome>(...);
container.AddService<IWelcome, CustomWelcome>();
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
2.4 強制性要求手工注冊
有些場景,我們不希望通過智能解析映射。而是在確保未注冊情況下返回 null 值。這個時候就需要用到 GetFixedService 方法。這個方法不會智能去解析,它只會判斷是否已經在注冊列表,如果沒有,直接返回 null 值。
2.5 更多
- Parent:上級容器
- ServiceTypes:所有服務類型。
- TypeValueNames:所有綁定到類型的值的名稱。
- ValueNames:所有值的名稱。
- DestroyAll():銷毀所有的映射。
- CreateChildLocator():創建基於當前服務容器的子服務容器。
- ContainsXXXX:判斷指定的類型或值是否已注冊。
- RemoveXXXX:刪除指定的類型或值。
3. 結束
關於 Aoite.Ioc 的簡單介紹,就到此結束了,如果你喜歡這個框架,不妨點個推薦吧!如果你非常喜歡這個框架,那請順便到Aoite GitHub Star 一下 :)
