[转][Prism]Composite Application Guidance for WPF(6)——服务
[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)——依赖注入容器
[转][Prism]Composite Application Guidance for WPF(6)——服务的更多相关文章
- 1: 介绍Prism5.0 Introduction to the Prism Library 5.0 for WPF(英汉对照版)
Prism provides guidance designed to help you more easily design and build rich, flexible, and easy- ...
- 下载并安装Prism5.0库 Download and Setup Prism Library 5.0 for WPF(英汉对照版)
Learn what’s included in Prism 5.0 including the documentation, WPF code samples, and libraries. Add ...
- Prism开发人员指南5-WPF开发 Developer's Guide to Microsoft Prism Library 5.0 for WPF (英汉对照版)
April 2014 2014四月 Prism provides guidance in the form of samples and documentation that help you e ...
- 3: 组件间的依赖管理 Managing Dependencies Between Components Using the Prism Library 5.0 for WPF(英汉对照版)
Applications based on the Prism Library are composite applications that potentially consist of many ...
- 使用Prism提供的类实现WPF MVVM点餐Demo
使用Prism提供的类实现WPF MVVM点餐Demo 由于公司开发的技术需求,近期在学习MVVM模式开发WPF应用程序.进过一段时间的学习,感受到:学习MVVM模式,最好的方法就是用MVVM做几个D ...
- Prism5.0开发人员指南内容 Contents of the Developer's Guide to Prism Library 5.0 for WPF(英汉对照版)
The Prism for WPF guide contains the following topics: Prism指南包含以下内容: Download and Setup Prism 下载并安装 ...
- Microsoft Prism安装使用教程 搭建WPF松耦合架构框架
Microsoft Prism安装使用教程 搭建WPF松耦合架构框架 Prism是由微软Patterns & Practices团队开发的项目,目的在于帮助开发人员构建松散耦合的.更灵活.更易 ...
- Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 中级篇
本着每天记录一点成长一点的原则,打算将目前完成的一个WPF项目相关的技术分享出来,供团队学习与总结. 总共分三个部分: 基础篇主要争对C#初学者,巩固C#常用知识点: 中级篇主要争对WPF布局与Mat ...
- Patterns in the Composite Application Library
Patterns in the Composite Application Library Inversion of Control https://www.codeproject.com/Artic ...
随机推荐
- Nginx配置两份日志记录
nginx配置 版本-1.4.4 --- access_log /alidata/log/nginx/access/wordpress1.log ; access_log /alidata/log/n ...
- 阿里云启动视频云V5计划,全面赋能生态合作伙伴
9月25 - 27日,主题为数·智的2019云栖大会在杭州举行.在第三天的智能视频云专场中,阿里云研究员金戈首次对外发布视频云V5计划,释放视频IT基础设施红利,赋能生态合作伙伴,共促大视频产业发展. ...
- c语言 局部变量做返回值 问题
一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了.因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错.但是如果返回的是局部变量的地 ...
- Python学习详细教程-武沛齐
目录 Python之路[第一篇]:Python简介和入门 Python之路[第二篇]:Python基础(一) Python之路[第三篇]:Python基础(二) Python之路[第四篇]:模块 Py ...
- [kuangbin带你飞]专题一 简单搜索 - B - Dungeon Master
#include<iostream> #include<cstdio> #include<string> #include<vector> #inclu ...
- ie6-8 avalon2 单页应用项目实战备忘
坑爹的ie,作为小组leader,尼玛,小伙伴儿们不乐意做的事情,我来做好了..心累... 如果,各位同学有定制开发ie6-8版本的需求,还是尽量不要用单页应用模式了,也不要用avalon这类mvvm ...
- map 与 lambda 的用法
# 列表中的每个元素进行*2lis = [10, 30, 70]f = map(lambda li: li*2, lis) # 操作lis中的每个元素.print(list(f)) # 返回来一个新的 ...
- POJ - 2406 ~SPOJ - REPEATS~POJ - 3693 后缀数组求解重复字串问题
POJ - 2406 题意: 给出一个字符串,要把它写成(x)n的形式,问n的最大值. 这题是求整个串的重复次数,不是重复最多次数的字串 这题很容易想到用KMP求最小循环节就没了,但是后缀数组也能写 ...
- 笔记-ubuntu19共享文件夹
这篇文章记录ubuntu和windows共享文件夹的步骤,环境是ubuntu19,两种方法,一种是图形化界面,一种是命令行. 图形化界面 打开文件软件,找到需要分享的文件夹,点击右键-属性-本地网络共 ...
- JS控制语句 编程练习 学生数据,分别是姓名、性别、年龄和年级,接下来呢,我们要利用JavaScript的知识挑出其中所有是大一的女生的的名字哦。
编程练习 在一个大学的编程选修课班里,我们得到了一组参加该班级的学生数据,分别是姓名.性别.年龄和年级,接下来呢,我们要利用JavaScript的知识挑出其中所有是大一的女生的的名字哦. 学生信息如下 ...