程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Composite Application Guidance for WPF(6)——服務

Composite Application Guidance for WPF(6)——服務

編輯:關於.NET

在Ioc和DI中,最熟悉的一個詞語便是服務(Service)了,關於Service的定義以及其與Component(組件)的一些小小區別,請參考Martin Fowler的這篇文章,我們這裡主要看看在Prism中是如何實現服務的注冊和使用的。

1,Service Locator (服務定位器)

這是必須首先討論的問題,當我們的一個類型對象要依賴另外一個服務方可生存的時候,我們應該如何引用這個服務呢?

最簡單的方式是如下的直接引用:

我們可以看到ClassA直接引用了其依賴的兩個服務ServiceA和ServiceB,這說帶來的壞處不言而喻,當然有人會說:“我會引用服務的接口而不是服務的實現”,Good,但無論怎樣,服務的具體實現類還是要被引用到的,而這種引用散亂地分布在系統各處,而你自己不得不去維護這些服務的生命周期,更可怕的是你所使用的服務必須是在編譯時便存在的。

與其讓客戶端對服務的依賴分散於系統各處,更好的一種做法是:讓一個專門的角色來統一創建和管理服務,這便是“服務定位器”:

我們看到,ClassA依賴於服務定位器,而服務定位器將去引用系統需要用到的服務,這所帶來的好處有一下幾點:

我們將類的具體實現和服務的具體實現隔離開來,當你需要某個服務時直接向服務定位器索取,服務定位器將為你返回具體的服務,這樣的話,當你需要替換服務的具體實現時就便得異常容易了

在編譯期間類所依賴的服務可以沒有具體實現,比如我們需要的具體服務是在運行時動態加載的話(在編譯時根本就不知道服務實現類的具體類型),這便很有用處。(之所以可以這樣,請關注本系列隨筆中的“動態模塊加載”相關內容)

你不必自己去維護服務的生命周期(這有兩個模式,如果你在注冊服務時是按“單例”模式注冊的,那麼服務加載器會幫你維護其生命周期,相反其則在你每次使用時New一個服務的新實例)

這為單元測試帶來便利,你不必每次都為類的實現去MOCK其依賴的具體服務

而對應到.NET框架,我們發現其已經為我們實現了一個服務定位器,這便是 System.ComponentModel.Design.ServiceContainer 類,打開該類的代碼(你可以使用Reflector來查看,或者.NET貌似開源的,但我更習慣於Reflector),你可以很清晰地發現其實質上是用一個Hashtable來保存服務與服務實例之間的映射,當你向定位器注冊一個服務時(public void AddService(Type serviceType, Object serviceInstance)),其會在內部的Hashtable中以serviceType為Key,serviceInstance為Value來添加一條記錄,當你想定位器索取服務時(public virtual Object GetService(Type serviceType)),其便將Hashtable中以serviceType為Key的Value返回。

2,Prism:容器就是定位器

在Prism中沒有專門的服務定位器,而是使用“依賴注入容器作”為“服務定位器”,關於其優缺點暫不討論,不過你可以到這裡查看人家的討論。不過我們可以這樣理解:Prism中Container的RegisterType<IMyService, CustomerService>()方法與服務定位器中的AddService(Type serviceType, Object serviceInstance)方法異曲同工,Container中的object Resolve(Type type);方法於服務定位器中的Object GetService(Type serviceType)方法如出一轍。

3,Prism中的基礎服務

在默認情況下,Prism會加載一些基礎服務到容器中,除非你在調用UnityBootstrapper的Run方法時將useDefaultConfiguration參數設成False。

其會加載如下的服務:

IModuleEnumerator:模塊枚舉器,這是必須加載的,其用於枚舉Project中所用到的各模塊,其默認的模塊枚舉器是null,所以我們必須在實際編碼過程中重寫UnityBootstrapper的GetModuleEnumerator()方法來提供一個我們實際使用的枚舉器

Prism為我們提供了3種枚舉器,

一是StaticModuleEnumerator,這是一個靜態枚舉器,我們需要調用其AddModule(Type moduleType, params string[] dependsOn)方法來手動向其中添加模塊,自然地,在AddModule方法時我們必須依賴於模塊的具體實現,所以我們無法在運行時動態加載模塊

二是DirectoryLookupModuleEnumerator,這是一個動態枚舉器,其通過查找指定路徑下的程序集中實現了“Microsoft.Practices.Composite.Modularity.IModule”接口的類型來作為模塊並加載進來。

最後一種是ConfigurationModuleEnumerator,這也是一種動態枚舉器,與DirectoryLookupModuleEnumerator不同的是,其是通過解析指定目錄下的(或應用程序根目錄下的)*.config文件來取得模塊並加載進來。

IContainerFacade:這不用多解釋,依賴注入容器是必須加載的。關於Prism是如何支持各種容器的,可以參考這篇文章 “How Prism supports using multiple IOC containers”

IEventAggregator:事件聚合器,這是一個比較有意思的“模式”,其是對“觀察者模式”的補充或者說一個變體,其用於解耦事件發布者和事件訂閱者。在Prism中便是按照這種模式來發布和訂閱事件的,關於Prism中的事件機制,我將在本系列隨筆的後續文章中專門討論。而如果你對EventAggregator模式感興趣的話,可以看看這裡:Event Aggregator 

RegionAdapterMappings:Region適配器映射,用於提供容器控件和Region容器之間的映射關系,比如ItemsControl是WPF的一個容器控件,要將其作為Prism的Region容器,那麼就應該為該控件提供一個適配器(ItemsControlRegionAdapter)來告訴Region如何與該控件進行適配(Adapt),之所以要這樣,是因為不同類型的容器控件管理其子控件的方式不同,那麼,與對應容器控件相適配的Region其所要采取的管理其View的方式要有所不同。

IRegionManager,Region管理器,用於管理Region的集合,並將這些Region附加(Attach)到指定的容器控件上,通過IRegionManager你可以添加或查找到指定的Region,並向Region中添加、刪除、激活View

IModuleLoader,模塊加載器,注意,其與IModuleEnumerator不同,IModuleEnumerator用於發現模塊,IModuleLoader用於加載或者說初始化模塊。它實質上是調用了容器的Resolve方法。

4,如何注冊與使用服務

如果你理解了上述1,2兩個小結,那麼關於“如何注冊和使用服務”這個問題就自然有了答案,但我這裡仍然簡單地說一下:首先,作為服務的注冊方,我們需要找一個地方來容納我們需要注冊的服務;作為服務的使用方,我們需要找一個對象來定位和提供我們所需要的服務;並且,我們說過,在Prism中“容器就是定位器”,所以,很簡單,通用依賴注入容器(IUnityContainer)便可以輕松地實現服務的注冊與使用了。

比如:

形如myContainer.RegisterInstance<IMyService>(myDataService);的方式來注冊,

形如IMyService result = myContainer.Resolve<IMyService>();的方式來使用;

關於語法層面如何書寫,你可以參考這裡:

深入 Unity 1.x 依賴注入容器之二:初始化 Unity;

深入 Unity 1.x 依賴注入容器之三:獲取對象

另外,我們注意到,在程序中可以取到IUnityContainer並像其中注冊服務的地方很多,但就一般而言:

我們對於那些基礎的共享的服務的注冊,一般將其放到Bootstrapper中注冊,比如我們重寫Bootstrapper的ConfigureContainer時注冊

public class MyBootstrapper : UnityBootstrapper
{
  protected override void ConfigureContainer()
  {
    Container.RegisterType<IMyService, MyService>();
    base.ConfigureContainer();
  }
}

對應單個模塊依賴的服務,我們一般將其放在模塊內部注冊,這樣的一個好處是,只有當模塊被加載的時候其所依賴的服務才會被加載。比如:

public class MyModule : IModule
{
  private IUnityContainer myContainer;
  
  public MyModule(IUnityContainer container)
  {
     myContainer = container;
  }
  
  public void Initialize()
  {
     Container.RegisterType<IMyService, MyService>();
  }  
}

注意,為什麼是IUnityContainer而不是IContainerFacade,你可以參考我的上一篇隨筆: [Prism]Composite Application Guidance for WPF(5)——依賴注入容器

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved