本节导读:
第四章介绍了模块化应用程序开发所必要的知识和需要注意的内容。而本部分通过讲述模块化应用程序开发所注意的必要概念,包括IModule接口,模块加载过程,模块列表,模块间通信,和依赖注入容器,引出微软所推荐的两款依赖注入框架——Unity和MEF。
在本章全部翻译完成后,我会在本章最后一部分补上全部的导读。
第四章 模块化应用程序开发
模块化应用程序指的是将一个应用程序划分成若干个可以组合的功能单元。一个模块封装了应用程序的一部分,并且通常是一系列相关的关注点。他们包含了含有用户界面和业务逻辑相关组件的集合,比如应用程序的某些功能,或者是一些系统基础模块,比如应用程序级的日志系统或者用户认证系统。模块之间相对独立但又可以通过松耦合手段进行通信。模块化应用程序可以使开发,测试,部署或者扩展应用程序变的更加方便。
举例来说,考虑一个个人银行。用户可以使用各种各样的功能,比如转账,付款,或者从他们的个人UI中更新个人信息。然而,这些功能都是封装到各个独立的模块中。这些模块互相之间使用如数据库或者web service等后端系统进行通信。应用程序服务将这些模块整合到一起并且提交用户交互内容。用户看起来就好像这是一个应用程序一样。
下图描述了多模块应用程序的设计思想:
4.1 构建多模块应用程序的好处
你可以已经使用类,接口,程序集来搭建一个结构优良的应用程序,并且它很好的体现了OO的设计原则。即便如此,除非特别注意,你的应用程序还是设计成为“一整块”(因为所有功能都是使用紧耦合的方式实现的),这样会使应用程序难以部署,开发,扩展,测试和维护。
从另一方面说,而应用程序模块化能够帮助你识别大的功能区并且可以将其分开开发和测试。这样的方式可以简化开发和测试的过程,而且也可以让你的应用程序更加灵活,在将来也可以更方便的扩展。
4.1.1 Prism为模块化开发所提供的内容
Prism为模块化应用程序开发和运行时模块管理提供支持。使用Prism的模块化开发功能可以帮助你节省测试和实现你自己的模块化框架的时间。Prism提供了以下的模块化开发功能:
l 注册和定位模块的模块列表,模块列表可以通过以下途径创建:
u 在模块中定义或者在XAML文件中定义
u 在WPF:在同一目录中自动发现所有模块,而不需要集中的逐一定义。(泽注:应该说的是MEF方式)
u 在WPF:使用配置文件定义
l 定义模块的元数据属性以说明初始化方式和依赖关系
l 与依赖注入容器集成以支持模块间以松耦合关系连接
l 在模块读取时:
u 依赖管理,包含了二次(duplicate)和循环检测以保证每个模块以正确的方式加载,并且只加载一次
u 通过需要时加载和后台下载的方式最小化程序载入时间;也就是说一些模块可以在需要的时候在后台加载和初始化
4.2 关键概念
本节介绍了与Prism模块化开发有关的关键概念,包括IModule接口,模块加载过程,模块列表,模块间通信,和依赖注入容器。
4.2.1 IModule:模块化应用程序的构建者
一个模块是一系列包装在一起并且可以独立开发,测试,部署,和集成到应用程序的功能和资源的集合。这个包可以是在XAP文件中的一个或多个程序集,他们可能是一个松散集合,也可能是集中放置。每个模块都有一个枢纽类以负责初始化和将本身的功能与应用程序集合,这个类会实现IModule接口。一个包中只要有一个实现IModule接口的类就足以判定这是一个模块。IModule接口只有一个称为Initialize的方法,通过该方法,可以定义任何需要添加到应用程序中的逻辑和功能。根据模块的功能,将视图注册到复杂UI中,添加应用程序需要使用的服务,或者扩展应用程序现有功能。以下是一个定义一个模块最基本的代码。
1 public class MyModule : IModule2 {3 public void Initialize()4 {5 // Do something here.6 }7 }
【注意】:除了使用IModule接口提供的Initialize方法,the Stock Trader RI使用了一种基于属性的定义法来注册视图,服务,和类型。
4.2.2 模块生命周期
在Prism中,模块以以下方法初始化:
l 模块注册/发现,模块在特定应用程序的模块列表中在运行时加载。该列表说明了模块是否需要加载,它们的位置,以及以何种程序被加载。
l 加载模块,模块所在的程序集被载入内存。这一阶段使模块通过网站,其它远程连接手段或者直接在本地文件中发现的途径被下载。
l 初始化模块,下载后,模块被初始化。也就是产生一个实例并且通IModule接口提供的Initialize方法初始化。
下图说明了模块的加载过程。
4.2.2 模块列表
ModuleCategory提供了应用程序所需要的模块信息。其本质是ModuleInfo类的集合。每个模块的名字,类型,位置,和其它模块中的属性信息都在ModuleInfo描述和记录。以下是上些通过ModuleInfo实例生成ModuleCategory的典型方法:
l 在代码中注册
l 在XAML中注册
l 使用配置文件注册(只有在WPF中可以)
l 通过本地目录自动发现(只有在WPF中可以)
你可以根据应用程序的需要选择发现或者注册机制。通过配置文件或者XAML可以让你不需要在项目中引用模块。使用目录自动发现则不需要将它们在某个文件中特别定义。
4.2.3 控制模块加载
Prism应用程序可以尽早加载模块,也就是所谓的“可用时加载”,或者当应用程序需要它们的时候加载,也就是所谓的“使用时加载”。在Silverlight应用程序中,模块可以应用程序一起下载,或者在应用程序启动后在必要时再下载。模块加载过程请参考如下内容:
l 模块如果是应用程序执行必备的,那么,必须同应用程序一同下载并且在运行时被初始化
l 如果模块包含了应用程序的常用功能,那么就可以在即将需要它的时候通过后台下载的方式加载(泽注:这种就是上文所述的“可用时加载”)
l 如果模块所包含的功能并不常使用(或者提供的功能不是其它模块所必需的)就可以选择在需要时加载
4.2.4 模块与应用程序的整合
Prism提供了以下两个类负责应用程序启动过程:UnityBootstrapper和MefBootstrapper。这些类用以创建和配置模块的发现和加载的管理。你可以通过几行代码重写配置方法以通过XAML文件,配置文件,本地目录的形式加载模块。
模块通过的Initalize方法与应用程序的其它部分整合。初始化的方法是多样的,它取决与你应用程序的结构和模块的内容。以下的内容是一般模块整合进应用程序中必做的事:
l 将模块的视图都添加到应用程序的导航结构中,这是使用视图发现或者视图注入构建复杂UI通常要做的事
l 订阅应用程序级的事件或者服务
l 向应用程序的依赖注入容器注入共享的服务
4.2.5 模块间通信
即使模块间是松耦合关系,但是模块间通信还是必不可少的。通信的方式有多种,每种方式都有其独特的优势。通常,解决方案中使用的会是这些方式的组合。以下说明了一些通信方式:
l 松耦合事件,模块可以告知某个事件已经发生。其它模块可以订阅这些事件以保证在事件发生时,它们会收到通知。松耦合事件是建立两个模块通信的轻量级方法;因而,它很容易实现。但是一个过于依赖事件的设计会很难维护,由其是那些为了实现一个目标而安排的事件。在这种情况下,应该考虑共享服务。
l 共享服务, 共享服务是一个可以通过通用接口访问的类。通常,共享服务是在公用程序集中定义,并且提供了系统层的服务,例如,用户认证,日志或者配置。
l 共享资源,如果你不想让两个模块直接通信,那么也可以通过共享资源来间接通信,比如数据库或者一些web service。
4.2.5 依赖注入和模块化应用程序
容器,比如Unity(Unity Application Block)和MEF(Managed Extensibility Framework),可以让你方便的使用控制反转(Inversion of Control,IoC)和依赖注入,它们都是设计模块的强力设计模式。它们可以让模块在不通过复杂代码说明引用的情况下获得这些引用关系。以此来提供代码的灵活性和可重用性。依赖注入在构建松耦合模块化应用程序中非常常用。Prism本身对构成应用程序和组件的依赖注入容器是不可知的。依赖注入容器的选择最大程序取决于你应用程序的需求。不过Microsoft也推荐了两款主打依赖注入框架——Unity和MEF。
patterns & practices Unity Application Block提供了一个完整的依赖注入容器所必要的内容。它提供了属性注入,构造函数注入和函数注入以供显示的向模块注入所需要的内容。它也提供了许多其它典型依赖注入容器所有的功能。
MEF(它也是.NET Framework 4和Silverlight 4的组成部分)通过提供基于依赖注入的模块来保证.NET程序的可扩展性,并且提供其它模块化应用程序开发所需要的功能。它使得应用程序可以在运行时发现模块并且以松耦合的方式整合到应用程序中。MEF是一个有良好扩展性和构建可能的框架,它包含了程序集和类的发掘,类的依赖分析,依赖注入,一些不错的程序集,和XAP下载性能。Prism提供了所有MEF的优点,如下文所述:
l 将模块中的类与XAP位置进行关联
l 在WPF或者Silverlight中通过XAML或者属性方式注册模块
l 在WPF中通过配置文件或者目录扫描的方式注册模块
l 模块加载时的状态跟踪
l 在使用MEF时的自定义模块元数据
当然,Unity与MEF都可以与Prism无缝连接。