[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. 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- ...

  2. 下载并安装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 ...

  3. 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 ...

  4. 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 ...

  5. 使用Prism提供的类实现WPF MVVM点餐Demo

    使用Prism提供的类实现WPF MVVM点餐Demo 由于公司开发的技术需求,近期在学习MVVM模式开发WPF应用程序.进过一段时间的学习,感受到:学习MVVM模式,最好的方法就是用MVVM做几个D ...

  6. 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 下载并安装 ...

  7. Microsoft Prism安装使用教程 搭建WPF松耦合架构框架

    Microsoft Prism安装使用教程 搭建WPF松耦合架构框架 Prism是由微软Patterns & Practices团队开发的项目,目的在于帮助开发人员构建松散耦合的.更灵活.更易 ...

  8. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 中级篇

    本着每天记录一点成长一点的原则,打算将目前完成的一个WPF项目相关的技术分享出来,供团队学习与总结. 总共分三个部分: 基础篇主要争对C#初学者,巩固C#常用知识点: 中级篇主要争对WPF布局与Mat ...

  9. Patterns in the Composite Application Library

    Patterns in the Composite Application Library Inversion of Control https://www.codeproject.com/Artic ...

随机推荐

  1. bzoj1568 Blue Mary

    题意:P:加入一条一次函数.Q:询问x位置的最大函数值. 标程: #include<bits/stdc++.h> using namespace std; ; int q,x,n; dou ...

  2. 阿里云POLARDB如何帮助猿辅导打造“孩子喜欢老师好”的网课平台?

    海量的题库.音视频答题资料.用户数据以及日志,对猿辅导后台数据存储和处理能力都提出了严峻的要求.而由于教育辅导行业的业务特点,猿辅导也面临着业务峰值对于数据库能力的巨大挑战.本文就为大家介绍阿里云PO ...

  3. 0924CSP-S模拟测试赛后总结

    50分-rank28 我是第二机房垫底大垃圾. 赛时T1和T2其实想到了正解??安慰自己罢了. 真正的CSP-S的赛后你还能和主办方争论说自己其实想到了正解要求人家硬给你个省一不成?? 出题人不知道到 ...

  4. 最大流拆点——hdu2732,poj3436

    一种很普遍的做法就是把一个带有容量的点拆成两个点,一个入点一个出点,链接两个点的边的权值为这个点的容量 hdu3732 #include<cstdio> #include<cstri ...

  5. 思维题+栈的应用——cf1092D有意思

    第一例很简单,把两个差为偶数的列不断合并即可 这种不需要撤销的合并相连数直接用栈来做 /* 如果相邻两列高度差为偶数 那么可以直接消去 */ #include<bits/stdc++.h> ...

  6. (3)mysql表和字段的操作

    创建表 create table name( id int, student ) ); 查看表结构 ****常用**** describe 表名; 修改表名 老表 rename 新表 ALTER TA ...

  7. mac idea解决快捷键的问题

    取消mac的快捷键 设置->键盘->快捷键 我这里取消的有:聚焦->显示聚焦搜索 应用快捷键->显示帮助菜单. 类似eclipse的自动提示错误的解决方案(quick fix ...

  8. pycharm同时使用python2.7和python3.5设置方法

    pycharm同时使用python2.7和python3.5设置方法 - CSDN博客https://blog.csdn.net/qwerty200696/article/details/530159 ...

  9. CCPC 2019 网络赛 1002 array (权值线段树)

    HDU 6703 array   题意:   给定一个数组 \(a_1,a_2, a_3,...a_n\) ,满足 \(1 \le a[i]\le n\) 且 \(a[i]\) 互不相同.   有两种 ...

  10. <随便写> 多线程的例子

    ''' 一个线程在使用这个共享的时候,其他线程必须等待他结束 通过"锁"实现,作用就是防止多个线程使用这片内存空间 进程:程序的一次执行 线程:cpu运算的基本调度单位 多线程:大 ...