[Prism]Composite Application Guidance for WPF(6)——服务

Author: 周银辉

Date: 2008-09-01

在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)——依赖注入容器

 

 

 

下一篇:[Prism]Composite Application Guidance for WPF(7) -- -- 模块-周银辉