[转][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 ...
随机推荐
- Hibernate与数据库交互方式和Hibernate常用的几个方法
第一种,适合sql语言水平比较高的人用 HQL(Hibernate Query Language) 面向对象的查询语言,与SQL不同,HQL中的对象名是区分大小写的(除了JAVA类和属性其他部分不区分 ...
- Ubunto 无法连接ssh客服端
解决办法: (1)查看ip地址是否冲突 我在单位的虚拟机ip地址是192.168.14.85,与其它机器冲突了.改成了192.168.14.83 (2)关闭Ubuntu14.04的防火墙 root ...
- python笔记三
# 数据读写不一定是文件,也可以在内存中读写 # StringIO就是在内存中读写str from io import StringIO f = StringIO() # 要把str写入StringI ...
- java编程规约一
提高开发效率,比较重视代码规范,尤其是可扩展性和可维护性,以及可读性.如果你是一个刚进公司的开发者,最好先问问前辈是否有 内部的开发规范,花点时间过一遍.即使提交代码没有review的步骤,自己心里应 ...
- 关于vlfeat做vlad编码问题
这里是官方文档,可以自己查看 在这里,只是想记录一下,我这几天学习vlfeat 做vlad编码的过程,便于以后整理 网上涉及到vlfeat做vlad编码资料较少,而官网上例子又相对简单,主要是那几个参 ...
- 爬虫所需要的文档和自动化文本driver下载地址,以及制作词云的文档,api等
Scrapy1.7.3文档 webdriver文档 webdriver下载地址 Chrom各版本下载地址 词云1.5文档 selenium中文文档 vue数据可视化文档 element开发组件 其他好 ...
- 基础数据类型汇总补充,python集合与深浅拷贝
一.基础数据类型汇总补充 1.查看str所有方法方式 2.列表:在循环中删除元素,易出错或报错(飘红) lis = [11,22,33,44,55] # for i in range(len(lis) ...
- springcloud系列12 config的使用
config组件分为server端和client端 config的原理: 就是当我们将配置文件放置在git上面,那么configserver就会去拉取相关配置文件至本地: 可以看到我本地是拉去了配置文 ...
- 2018-8-10-如何删除错误提交的-git-大文件
title author date CreateTime categories 如何删除错误提交的 git 大文件 lindexi 2018-08-10 19:16:51 +0800 2018-2-1 ...
- opensuse 通过composer安装drush工具
由于笔者的opensuse已安装好composer,所以按照官方网站的文章 Installing/Upgrading Drush on Ubuntu,使用composer形式安装drush工具. co ...