[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. Java怎样获取字符串最后出现的位置

    lastIndexOf();表示获取字符串最后出现的位置,倒数的位置 @Test /** * lastIndexOf();//获取字符串最后出现的位置,倒数的位置 * */ public void f ...

  2. js对象 事件

     对象  创建  var   myObject = {};/* 声明对象字面变量*/ 添加值myObject.name="Jener";myObject.age=25; 代码格式 ...

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

    献上了自己的第二次爆零. 最近考试持续低迷.受同桌影响是一方面,自己的状态不行也是一方面,根本还是实力不行. 昨天T1是签到题.然而并没有发现这个事实.并不会打…… 无意围观同桌秒切T1,秒过样例,长 ...

  4. 最大流dicnic——hdu1532模板题

    #include<bits/stdc++.h> using namespace std; #define maxn 1005 #define ll long long const ll i ...

  5. t检验中的t值和p值是什么关系_t检验和p值的关系

    t检验中的t值和p值是什么关系_t检验和p值的关系 t检验中通过样本均值 总体均值 样本标准差 样本量 可以计算出一个t值,这个t值和p值有什么关系? 根据界值表又会查出一个数,这个数和t值比较,得出 ...

  6. dashboard服务

    1.上传镜像,并导入,打标签 2.创建dashboard的deployment和service apiVersion: extensions/v1beta1 kind: Deployment meta ...

  7. 字段username没有默认值查询(设计数据库一定要养成好习惯,不是主键最好设置为可以为空)

    今天创建了一个表,但是username作为外键(不是主键)没有设置为可以为空,结果提交表单时忘记写username就报错了

  8. Echarts——更改仪表盘方向和颜色

    做小项目需要用到仪表盘,官方给出的颜色设置如下: 而我想要如下样式的: 最后,经过一番折腾算是搞成了如下样式效果: 要达到上面效果关键在于设置Echarts的如下两处js代码: 1.大小值要颠倒,因为 ...

  9. 微软云数据库SQL Azure

  10. LINQ用法总结

    之前一直用sql和拉姆达表达式,一直感觉linq不好用.用熟练了感觉期功能好强大,查询性能例外考究.这里讲讲基本用法. 内联查询: var list2 = (from a in db.Role whe ...