Unity應用架構設計(8)——使用ServiceLocator實現物件的注入
物件的 『注入』 是企業級軟體開發經常聽到的術語。如果你是一個 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();
...
}
}
遺憾的是,Bar 和 SomeService 又耦合了。然後你又改成這樣:
public class Bar
{
ISomeService _service;
public Bar(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
var foo = new Foo(_service);
foo.DoSomething();
...
}
}
通過建構函式傳遞引數,Bar 和 SomeService 解耦了。但你打算怎樣去例項化 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 <object>,本質上是一段匿名函式,只有當真正需要的時候,執行這段匿名函式,返回物件。這是一個非常好的設計,也是懶載入的核心。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 3D Framework Designing(7)——IoC工廠理念先行』。這兩篇文章對於初學者來說是有難度的,因為很多概念都是Web開發經常遇到的,如果需要額外的資料,建議翻閱 《設計模式》 相關書籍。
原始碼託管在Github上,點選此瞭解