Unity應用架構設計(8)——使用ServiceLocator實現物件的注入

木宛城主發表於2017-07-22

物件的 『注入』 是企業級軟體開發經常聽到的術語。如果你是一個 Java 程式設計師,一定對注入有著深刻的映像。不管是SSH框架還是SSM框架,Spring 全家桶永遠是繞不過去的彎。通過依賴注入,可以有效的解耦應用程式。在uMVVM框架中,我提供了另外一種物件注入的方式,稱為Service Locator 『服務定位模式』 。與Spring的依賴注入不同的是,Service Locator 內部以字典的形式維護了物件的依賴關係,外部通過Key的形式獲取 『Resolve』 到對應的Value,從而達到解耦。

為什麼要注入物件

簡而言之,為了解耦,達到 不去依賴 具體的物件。

實際上解耦是個非常 『虛』 的概念,只有軟體到達一定的複雜度之後才會明白解耦和的好處,對於一個簡單如『Hello World』程式而言,你很難理解為什麼需要解耦。

假設有個 Foo 類,需要通過呼叫 SomeService 物件的方法去執行一些任務。很簡單的需求,你可能會這樣寫:

public class Foo
{
    ISomeService _service;

    public Foo()
    {
        _service = new SomeService();
    }

    public void DoSomething()
    {
        _service.PerformTask();
        ...
    }
}複製程式碼

這當然沒問題,但有隱患,Foo 緊耦合了 SomeService,當需求變了,你不得不開啟 Foo 類,然後找到建構函式,重新呼叫另外的 Service,改完之後編譯,然後部署、測試等等。如果是Web程式,你還得在等到晚上去部署。

既然緊耦合了,那就解耦,你可能會這樣寫:

public class Foo
{
    ISomeService _service;

    public Foo(ISomeService service)
    {
        _service = service;
    }

    public void DoSomething()
    {
        _service.PerformTask();
        ...
    }
}複製程式碼

這樣很不錯,Foo 與具體的 Service 解耦了,那怎樣去例項化 Foo 呢?比如有一個 Bar 類:

public class Bar
{
    public void DoSomething()
    {
        var foo = new Foo(new SomeService());
        foo.DoSomething();
        ...
    }
}複製程式碼

遺憾的是,BarSomeService 又耦合了。然後你又改成這樣:

public class Bar
{
    ISomeService _service;

    public Bar(ISomeService service)
    {
        _service = service;
    }

    public void DoSomething()
    {
        var foo = new Foo(_service);
        foo.DoSomething();
        ...
    }
}複製程式碼

通過建構函式傳遞引數,BarSomeService 解耦了。但你打算怎樣去例項化 Bar 呢?

額...(-。-;)

Spring中的依賴注入

Spring中將上述 Foo、Bar 類對SomeService的依賴關係,通過建構函式或者setter方法來實現物件的注入。

<!-- 建立物件:Bar-->
<bean id="barId" class="com.xxx.Bar" >
    <property name="service" ref="someServiceId"></property>
</bean>

<!-- 建立SomeService例項 -->
<bean id="someServiceId" class="com.xxx.SomeService"></bean>複製程式碼

可以看到Spring將依賴關係配置到XML中,在執行時,從IoC容器工廠獲取 『Bean(即:例項)』 並將依賴關係注入。

難道我們需要在Unity3D 中定義XML來配置嗎?這會不會太麻煩了?

使用ServiceLocator實現物件的注入

其實物件的 『注入』 有很多實現方式,依賴注入 『DI』 只是其中一種,大名鼎鼎的Spring框架就是非常優秀的依賴注入框架,而uMVVM中實現的注入式通過ServiceLocator實現。

什麼是ServiceLocator?

簡單說ServiceLocator內部以字典的形式維護了物件的依賴關係,外部通過Key的形式獲取到對應的Value,從而達到解耦,如下圖所示:

要實現物件的 『注入』 ,還缺一個非常重要的物件,就是IoC容器工廠,所有需要被注入的物件都是由容器工廠建立。那我們哪裡去找工廠呢?還記得上篇文章的內容了嗎?我們已經預先定義了3種不同建立物件的工廠,他們分別為 Singleton Factory,Transient Factory以及 Pool Factory,這些就是我們需要的IoC工廠。

既然 ServiceLocator內部以字典的形式維護了依賴關係,那麼首先需要建立一個字典:

 private static readonly Dictionary<Type, Func<object>> Container = new Dictionary<Type, Func<object>>();複製程式碼

注意到字典的Value了嗎,這是一個 Fun ,本質上是一段匿名函式,只有當真正需要的時候,執行這段匿名函式,返回物件。這是一個非常好的設計,也是懶載入的核心。Swift 和 C# 4.0 的Lazy 核心和程式碼就是匿名函式。

我們再對Service Locator進行增強,既然要通過字典來維護依賴關係,那我們必須往字典裡註冊它們,結合我們的工廠,通過ServiceLocator獲取的物件可以是單例Singleton物件或者臨時Transient物件:

/// <summary>
/// 對每一次請求,只返回唯一的例項
/// </summary>
/// <typeparam name="TInterface"></typeparam>
/// <typeparam name="TInstance"></typeparam>
public static void RegisterSingleton<TInterface, TInstance>() where TInstance : class, new()
{
    Container.Add(typeof(TInterface), Lazy<TInstance>(FactoryType.Singleton));
}
/// <summary>
/// 對每一次請求,只返回唯一的例項
/// </summary>
/// <typeparam name="TInstance"></typeparam>
public static void RegisterSingleton<TInstance>() where TInstance : class, new()
{
    Container.Add(typeof(TInstance), Lazy<TInstance>(FactoryType.Singleton));
}
/// <summary>
/// 對每一次請求,返回不同的例項
/// </summary>
/// <typeparam name="TInterface"></typeparam>
/// <typeparam name="TInstance"></typeparam>
public static void RegisterTransient<TInterface, TInstance>() where TInstance : class, new()
{
    Container.Add(typeof(TInterface),Lazy<TInstance>(FactoryType.Transient));
}
/// <summary>
/// 對每一次請求,返回不同的例項
/// </summary>
/// <typeparam name="TInstance"></typeparam>
public static void RegisterTransient<TInstance>() where TInstance : class, new()
{
    Container.Add(typeof(TInstance),Lazy<TInstance>(FactoryType.Transient));
}

private static Func<object> Lazy<TInstance>(FactoryType factoryType) where TInstance : class, new()
{
    return () =>
    {
        switch (factoryType)
        {
            case FactoryType.Singleton:
                return _singletonObjectFactory.AcquireObject<TInstance>();
            default:
                return _transientObjectFactory.AcquireObject<TInstance>();
        }

    };
}複製程式碼

可以看到,其實本質上真的很簡單,就是一個 Key:Value 形式的字典,最後提供一個Resolve方法,可以通過Key來獲取對應的物件:

/// <summary>
/// 從容器中獲取一個例項
/// </summary>
/// <returns></returns>
private static object Resolve(Type type)
{
    if (!Container.ContainsKey(type))
    {
        return null;
    }
    return Container[type]();
 }複製程式碼

使用起來也非常方便,在一個全域性初始化檔案中去定義這些依賴關係:

ServiceLocator.RegisterSingleton<IUnitRepository,UnitRepository>();複製程式碼

然後在你的任何業務程式碼中以Resolve的形式獲取物件:

ServiceLocator.Resolve<IUnitRepository>();複製程式碼

小結

使用建構函式或者setter方法依賴注入也好,還是使用ServiceLocator也罷,它們本質上都是去解耦。物件的注入一般需要結合IoC容器,我們已經定義了3種不同的IoC工廠容器。詳細可以翻閱前一篇文章:『Unity應用架構設計(7)——IoC工廠理念先行』。這兩篇文章對於初學者來說是有難度的,因為很多概念都是Web開發經常遇到的,如果需要額外的資料,建議翻閱 《設計模式》 相關書籍。

原始碼託管在Github上,點選此瞭解
歡迎關注我的公眾號:

相關文章