當系統中的組件需要調用某一服務來完成特定的任務時,通常最簡單的做法是使用new關鍵字來創建該服務的實例,或者通過工廠模式來解耦該組件與服務的具體實現部分,以便通過配置信息等更為靈活的方式獲得該服務的實例。然而,這些做法都有着各自的弊端:
- 在組件中直接維護對服務實例的引用,會造成組件與服務之間的關聯依賴,當需要替換服務的具體實現時,不得不修改組件中調用服務的部分並重新編譯解決方案;即使采用工廠模式來根據配置信息動態地獲得服務的實例,也無法針對不同的服務類型向組件提供一個管理服務實例的中心位置
- 由於組件與服務之間的這種關聯依賴,使得項目的開發過程受到約束。在實際項目中,開發過程往往是並行的,但又不是完全同步的,比如組件的開發跟其所需要的服務的開發同時進行,但很有可能當組件需要調用服務時,服務卻還沒完成開發和單體測試。遇到這種問題時,通常會將組件調用服務的部分暫時空缺,待到服務完成開發和單體測試之后,將其集成到組件的代碼中。但這種做法不僅費時,而且增大了出錯的風險
- 針對組件的單體測試變得復雜。每當對組件進行單體測試時,不得不為其配置並運行所需要的服務,而無法使用Service Stub來解決組件與服務之間的依賴
- 在組件中可能存在多個地方需要引用服務的實例,在這種情況下,直接創建服務實例的代碼會散布到整個程序中,造成一段程序存在多個副本,大大增加維護和排錯成本
- 當組件需要調用多個服務時,不同服務初始化各自實例的方式又可能存在差異。開發人員不得不了解所有服務初始化的API,以便在程序中能夠正確地使用這些服務
- 某些服務的初始化過程需要耗費大量資源,因此多次重復地初始化服務會大大增加系統的資源占用和性能損耗。程序中需要有一個管理服務初始化過程的機制,在統一初始化接口的同時,還需要為程序提供部分緩存功能
要解決以上問題,我們可以在應用程序中引入服務定位器(Service Locator)企業應用體系結構模式。
服務定位器(Service Locator)模式
服務定位器(Service Locator)模式是一種企業級應用程序體系結構模式,它能夠為應用程序中服務的創建和初始化提供一個中心位置,並解決了上文中所提到的各種設計和開發問題。服務定位器模式主要有以下幾種參與者:
服務(Service)
服務是服務定位器需要返回給調用方的具體實例。比如服務定位器可以根據調用方的需要,返回一個向控制台輸出信息的服務,或者返回一個向文件系統輸出信息的服務。在這種情形下,這兩種服務可能會有着不同的接口:對於向控制台輸出信息的服務而言,它只需要接收一個參數(即需要輸出的信息)就可以完成輸出任務;而對於向文件系統輸出信息的服務而言,它不僅要獲得待輸出的信息,而且還要獲得一個正確的文件名,以便將信息輸出到這個文件中。
因此,在實際應用中,我們通常會為不同的服務類型設計不同的接口,而服務定位器則應該根據調用方給定的服務類型,返回相應的服務實例。
服務工廠(Service Factory)
服務工廠是工廠模式的一種實現,它的職責是創建並初始化某種類型的服務。例如,向控制台輸出信息的服務,是由一個工廠創建並初始化的;而向文件系統輸出信息的服務,則是由另一個工廠所創建。由此可見,不同的服務類型有其特定的服務工廠,在實際應用中,服務工廠與一個特定的服務接口所對應。
使用服務工廠不僅可以解耦服務的定義部分和具體實現部分,應用程序無需重新編譯即可變更服務的不同實現方式,而且對於初始化過程需要消耗大量資源的服務而言,服務工廠還能夠提供緩存功能,從而提高應用程序的性能。
Initial Context
由於不同的服務需要由不同的服務工廠創建並初始化,因此對於服務定位器來說,還需要一個特定的管理器來統一管理這些服務工廠,InitialContext就充當了這個角色。在調用方向服務定位器請求一個服務的實例時,服務定位器通過InitialContext獲得服務工廠的實例,然后由服務工廠創建服務實例並返回給調用方。使用InitialContext的優點是,它簡化了服務定位器的職責,並為服務工廠的管理和緩存提供了有力保障。
服務定位器
服務定位器為調用方獲得所需服務的實例提供了中心位置。
模式的實現
假設某系統中存在兩種類型的服務,一種服務會將調用方傳遞的信息輸出到控制台,我們將其稱為控制台輸出服務;而另一種服務則會將調用方傳遞的信息輸出到文件系統中,我們將其稱為文件系統輸出服務。通過分析可以了解到,這兩種服務的接口是不同的:控制台輸出服務只需要獲得待輸出的信息即可,而文件系統輸出服務還需要獲得一個文件名,以便將信息輸出到指定的文件中。因此,針對這兩種不同的服務,需要設計兩種接口定義,這一點是顯而易見的。更進一步,為了隱藏服務的初始化過程,並提供一定的緩存機制,我們還將針對每種服務類型(或者說每種服務接口)設計一個服務工廠,其職責在上面已經說過了,在此就不再贅述。在實際項目中,我們可以為服務工廠提供一個ServiceFactory的抽象基類,這樣做的目的是為了將與諸如緩存相關的機制統一在基類中實現,而子類則直接負責服務實例的創建和初始化即可。
接下來Initial Context的實現就相對比較簡單了,只需要針對不同的服務類型維護服務工廠即可。我覺得Initial Context的實現也不是必須的,對於一些簡單的應用場景完全可以由Service Locator替代。但是在實現了Initial Context的設計中,Service Locator則需要將獲取服務實例的工作轉交給Initial Context執行。事實上IoC Containers就是Service Locator的實現,但它們往往都比較復雜,包括諸如循環引用的解析和生命周期管理等復雜功能,時間有限,我也沒深入地去研究IoC框架,也不打算進一步探討了。
根據上面的分析,Service Locator有着如下的結構:
執行過程可以用下面的UML序列圖描述:
可以【單擊此處】下載上述實現的源代碼(思考題:請將代碼中Service Locator改為泛型實現)。
Byteart Retail案例中的實現
在Byteart Retail案例中,服務定位器模式的實現已經“退化”成對現有IoC容器(Enterprise Library Unity)的封裝,封裝的目的是為了簡化開發過程,從而為應用程序訪問IoC容器提供一個中心位置。另外,這樣的封裝解耦了應用程序對IoC容器的依賴,比如在Byteart Retail案例的源代碼中,並沒有直接引用IoC容器,而是通過ServiceLocator類進行代理,這就為今后更換服務定位器的實現提供了便捷(當然一般情況下也不會去更換這種組件)。
有關ServiceLocator類的代碼,請參考Byteart Retail案例的源代碼中ByteartRetail.Infrastructure命名空間下的ServiceLocator類。
參考資料
- Service Locator Pattern(Wikipedia):http://en.wikipedia.org/wiki/Service_locator_pattern
- Service Locator Pattern(Core J2EE Patterns):http://www.oracle.com/technetwork/java/servicelocator-137181.html
- Service Locator Pattern(MSDN):http://msdn.microsoft.com/en-us/library/ff648968.aspx
- 依賴注入(Dependency Injection):http://msdn.microsoft.com/en-us/library/dd458879.aspx
- 控制反轉(Inversion of Control):http://msdn.microsoft.com/en-us/library/dd458907.aspx