Aoite 系列(02) - 超動感的 Ioc 容器


Aoite 系列(02) - 超動感的 Ioc 容器

Aoite 是一個適於任何 .Net Framework 4.0+ 項目的快速開發整體解決方案。Aoite.Ioc 是一套解決依賴的最佳實踐。

說明: Aoite 是一套快速開發整體解決方案。它不是只有 ORM 或者 Ioc 之類的。框架的內容還是算有點龐大。我需要一點一點的將文章和教程編寫出來,如果加上將其每一部分和其他框架進行比較更需要花費時間。所以所有的入門篇都會簡單的介紹用法,目的是讓使用人員快速入門。若是您想要更快的了解這套框架,可以從單元測試入手。

【Aoite 系列 目錄】

趕緊加入 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 模塊有一套類型映射的策略。它是這樣的一步一步的匹配:

  1. 判定 IWelcome 是否已經手工注冊(container.AddService)。
  2. 判定上級 Ioc 容器是否已手工注冊 IWelcome
  3. 判定是否禁用了自動解析的功能(IocContainer.DisabledAutoResolving),成立則直接返回 null 值。
  4. 判定 IWelcome 是否定義了 DefaultMappingAttribute 特性。
  5. 判定是否為基類或值類型,成立則直接返回 null 值。
  6. 嘗試觸發 IocContainer.MapResolve 事件獲取映射類型。
  7. 嘗試觸發 ObjectFactory.MapResolve 靜態事件獲取映射類型。
  8. 如果以上條件都找不到映射的類型,將會從當前所有已加載的程序集中滿足以下條件的類型(優先級從上至下):
    • namespace.DefaultWelcome
    • namespace.Welcome
    • namespace.FakeWelcome
    • namespace.MockWelcome
  9. 如果以上的條件無法滿足,將會返回一個 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 一下 :)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM