[Prism]Composite Application Guidance for
WPF(6)——服务
周银辉
在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)——依赖注入容器