4: 模块化应用程序开发(纯汉语版)
模块化应用程序是指由松耦合的功能单元(模块)集成在一起的大型应用。一个客户端模块封装了程序的一部分功能和相关问题。模块可以使一些相关组件的集合,例如程序功能,包括界面和业务逻辑,或是程序基础架构,例如日志或是授权用户等程序级别的服务。模块之间互相独立又可以松耦合通信。使用模块化应用程序设计是开发测试,部署和维护你的应用程序更加简单。
举个例子,一个私人银行程序。用户可以访问多种功能,例如转账,支付账单,并更新个人信息。然而,场景背后,每个功能都封装到一个单独的模块。这些模块互相交换,或是和后台系统交互,例如数据服务和web服务。程序服务集成多不同模块中的各种组件,用以处理和用户的交流。用户看到的视图看起来就是一个程序。
下图说明了多模块的模块化应用程序设计。
构建模块化应用程序的好处
你可能已经构建一个架构良好的程序,使用程序集,接口,和类,并采用良好的面向对象设计原则。即便如此,除非你非常小心,你的程序设计仍然是“铁板一块”(所有的功能都是以一个紧耦合的方式实现的),很难再开发,测试,扩展,维护。
模块化程序方法,另一方面,可以帮助你确定程序的大型功能区域,可以单独的开发和测试它。这让开发和测试更简单,也让程序更灵活和易于扩展。模块化方法可以使你整个应用程序架构更灵活和易维护,因为他允许把你的程序分块管理。每块都封装了特殊功能,并且能够清晰的集成,松耦合通信。
对模块化应用程序开发的支持
Prism提供模块化应用程序开发支持,并且可以在运行时管理模块。使用Prism的模块化开发功能可以解决你的时间,因为你不需要实现你自己的模块框架。Prism支持以下模块化应用程序开发特性:
- 一个模块目录记录了命名的模块和每个模块的位置;你可以用以下方式创建模块目录:
- 用代码或XAML定义模块目录
- 通过发现一个路径来加载其路径下的所有模块,这样就不需要定义明确的模块目录了。
- 通过一个配置文件定义模块目录
- 为模块声明元数据以支持初始化模式和依赖项。
- 集成依赖注入容器以支持模块间松耦合
- 模块加载:
- 依赖项管理,包括重复和循环检测,确保模块按正确的顺序加载,且只加载和初始化一次
- 点播和后台下载模块,以减少应用程序的启动时间;模块可以在后台加载和初始化,或者当需要它们是加载。
核心概念
此节结束Prism模块化的核心概念,包括IModule 接口,模块加载过程,模块目录,模块间的通信,依赖注入容器。
模块化应用程序的构建块
一个模块是一个功能和资源的逻辑集合,可以单独开发测试,部署,和集成到程序里(车轱辘话)。一个包可以使一个或多个程序集。每个模块有一个中央类,它的职责是初始化模块并集成它的功能到应用程序里。这个类需实现IModule 接口。
注意:有一个实现IModule 接口的类就足以说明这个包可以作为一个模块。
IModule 接口一个单一方法,叫做Initialize,你可以在其中实现以下需要的初始化和集成模块功能到程序的逻辑。取决于模块目的,它可以注册视图到组合式界面,添加额外的服务到程序,或是扩展程序功能,下面代码展示最基本的模块实现。
public class MyModule : IModule { public void Initialize() { // Do something here. } }
注意: |
---|
处理使用IModule 接口初始化模块,股票操盘程序使用了一个声明的,基于特性的方法来注册视图,服务和类型。 |
模块生命周期
Prism中模块加载过程如下:
- 注册/发现模块. 哪些模块被加载到程序,是定义在一个模块目录里的。木块目录包含的信息有模块加载方式,模块的位置,模块的加载顺序。
- 加载模块. 将包含模块的程序集加载到内存。模块可能从远程位置或本地路径获取。
- 初始化模块.接下来该模块初始化了.这意味着创建模块的实例并调用使用了IModule 接口的类中的Initialize 方法。
下图展示模块加载过程。
模块目录
ModuleCatalog 保持着模块的相关信息。目录基本上就是一个ModuleInfo 的集合类。每个模块都在其中记录名称,类型,位置,以及模块中的其它属性。这有一些通常的添加ModuleInfo 实例到ModuleCatalog 中的方法:
- 代码注册模块
- 注册模块
- 配置文件注册模块
- 在磁盘上某一路径发现模块
根据我们的需求来选取不同的注册及发现模块机制。使用配置文件或XAML文件允许你的程序不引用模块,使用一个路径可以允许你的程序不用配置它们。
加载模块时控制
Prism程序可以尽快初始化模块,叫做“可用时”,或者当程序需要它们是,叫做“点播”。考虑以下方式加载模块:
- 模块需要在程序启动时就加载。
- 模块包含的功能几乎所有模块都得用,那么模块当可用时加载。
- 模块包含很少使用的功能(或是其他模块的可选项支持),可以以点播方式加载和初始化模块。
考虑你是如何分割你的应用程序,通用用法方案,程序启动时,下载的数量和大小,以确定如何配置模块用于下载和初始化。
集成模块到应用程序里
Prism提供的引导类: UnityBootstrapper 或 MefBootstrapper. 这些类可以使用创建和配置模块管理器去发现和加载模块。你可以重写配置方法去注册模块,使用一个XAML文件,一个配置文件,或是一个本地路径。
使用模块Initialize 方法去集成模块。你可以用多种方式初始化模块内容,具体取决于您的应用程序的结构和模块的内容。
- 添加模块视图到应用程序导航结构。这是公共的方法当使用视图发现或视图注入创建组合式UI程序。
- 定义程序级别的事件或服务。
- 注册共享服务到应用程序的依赖注入容器
模块间的通信
即使模块应该有相互之间的低耦合,模块间相互通信很常见。这有一些松耦合的通信方式,各有好处。通常,可以组合他们作为解决方法。下面就是这些方式:
- 松耦合事件。一个模块可以广播已经发生了某些事件。其他模块可以订阅这些事件,使事件发生时收到通知。松耦合事件是一个轻量级方式建立两个模块的通信;因此,它也很容易实现。然而,过于依赖事件也会让程序变得难以维护,尤其是当许多事件都必须精心策划,共同完成一个任务。在这种情况下,更好的方式是考虑使用共享服务。
- 共享服务. 一个共享服务,是可以由一个公用的接口访问的类。通常,共享服务放在一个共享程序集,并提供全系统服务。例如身份验证,日志,或是配置。
- 分享资源. 如果你不想要模块之间或许引用,你也可以通过共享资源联系它们,例如数据库或一系列web服务。
依赖注入和模块化应用程序
像Unity和MEF这样的容器允许你轻松使用控制反转和依赖注入,这是一种非常强大的设计模式,帮助你组合组件以一种松耦合的方式。它允许组件获取其他组件的引用。而不用硬编码这些引用。从而促进更好的代码重用和提高灵活性。依赖注入是非常有用的,当构建松耦合,模块化应用程序。Prism被设计和依赖注入容器组合组件到程序不相关。容器的选择权在你,这很大程度上取决于你的应用需求和喜好。然而,有来自微软的两个主要依赖注入框架值得考虑——Unity和MEF。
模式和实践的Untiy应用程序块提供了一个全功能的依赖注入容器。它支持基于属性和基于构造函数注入和策略注入,它允许你透明地注入组件之间的行为和政策;它也支持许多其他功能,是典型的依赖注入容器。
MEF(已经是.NET Framework 4.5的一部分了)通过支持依赖注入构建可扩展的.NET应用程序——基于组件组成,并提供了支持模块化的应用程序开发其他功能。它允许一个应用程序在运行时发现组件然后以一种松耦合的方式集成这些组件到程序里。MEF是一个伟大的可扩展性和组合框架。它包括程序集和类型发现,类型依赖解析,依赖注入,和一些程序集下载功能,Prism支持MEF特性的这些优势,如下:
- 模块也通过XAML和代码特性注册
- 模块可以通过配置文件和路径浏览注册
- 模块加载时进行状态追踪
- 使用MEF时,模块声明自定义元数据
Untiy和MEF依赖注入容器都可以和Prism很好的一同工作。
关键决定
第一个决定是你是否想开发一个模块化解决方案。构建模块化的应用程序如前一节中所讨论的有许多好处,但得在时间允许和你真的需要这些好处。如果你决定开发一个模块化解决方案,还有些事要考虑:
- 确定你要使用的框架。你可以创建你的模块化框架,使用Prism,MEF,或是其他框架。
- 确定怎样组织你的解决方案。定义一个模块化架构的方法时定义每个模块的边界,包括哪些组件是哪个模块的哪一部分。你可以决定使用模块化来简化开发。也可以控制程序是否需要插件或可扩展的架构。
- 确定怎样分割你的模块。模块可以根据需求分割,例如,按功能区,提供程序模块,开发团队和部署要求。
- 确定程序将提供给所有模块的核心服务。举个例子核心服务可以使错误远程处理或是一个身份验证和授权服务。
- 如果你使用Prism,确定你注册模块到模块目录的方法。对于WPF,你可以用代码,XAML,配置文件,或是在一个磁盘路径发现模块来注册模块。确定你的模块通信和依赖策略。模块将需要互相通信,你将需要处理模块之间的依赖关系。
- 确定你的依赖注入容器。通常,模块化系统需要依赖注入,控制反转,或服务定位器来实现松耦合和动态加载和创建模块。Prism可用时使用Untiy,MEF,或其他容器并提供Unity或MEE类库。
- 减少应用程序的启动时间。可以考虑点播和后台下载模块,以减少应用程序的启动时间。
- 确定开发需求。你将需要考虑怎样部署你的应用程序。
下节提供了这些决定更详细的讨论。
分割你的程序到模块里
当你用模块化方式开发你的应用程序,你构建的应用程序分离到各个客户端模块,可以独立开发,测试和部署。每个模块将封装的应用程序的整体功能的一部分。你首先要考虑的是怎样分离你的功能到各个模块。
一个模块应该封装一组相关关注点和又一堆相应职责。一个模块可以使纵向服务或是横向服务。大型应用程序两种服务都有。
一个用垂直服务组织的应用程序
一个大型应用可以由垂直服务和横向层组成。模块的一些例子包括以下内容:
- 一个包含一个特定的应用程序的功能的模块,例如股票操盘参考实现的新闻模块。
- 一个包含一个特定的子系统或功能为一组相关的用例的模块,例如采购,发票或总帐
- 一个包含基础设施服务的模块,如日志记录,缓存和授权服务,或Web服务
- 一个包含业务逻辑的服务,例如Siebel CRM 和 SAP(什么东东),除了其它内部系统。
一个模块应该对其它模块的依赖尽量小。当一个模块要依赖其它模块时,应该有个松耦合的接口定义,取代具体类型。或是通过EventAggregator 来联系其他模块。
模块化的目标是分割程序,使其灵活,易维护,稳定,功能和技术可以自由添加删除。最好的方式来达成这些是设计你的模块尽量独立,有良好的接口设计,尽可能的分离。
确定项目模块分布
这有一些方式创建模块包。最常用的方式是为模块创建一个单一程序集。这有助于保持逻辑模块分开,促进适当封装。这样一更容易进行部署模块。然而,程序集也可以包含多个模块,一些情况下需要解决方案中的项目少些。对于一个大型项目,可能有10到50个模块,把每个模块都分离到单独的程序集会影响Visual Studio的表现。一些时候可以让一些模块由自己的解决方案以方便管理。
使用依赖注入实现松耦合
一个模块可能依赖于由宿主应用程序或由其他模块提供的组件和服务。Prism支持模块之间的依赖关系登记的能力以便于它可以以正确的方式加载和初始化。Prism还支持模块当它们被加载到应用程序时进行初始化。在模块初始化期间,模块可以检查其它需要引用其他组件和服务,注册它包含的任何组件和服务,为了他们对模块有用。
一个模块应该使用独立的机制来获得外部接口实例取代直接实例化一个具体的类型,例如通过使用依赖注入容器或工厂服务。例如Untiy或MEF的依赖注入容器允许自动获取它需要通过依赖注入的接口和类型的实例。Prism集成Unity和MEF去允许模块更简单的使用依赖注入。
下图展示当模块被加载时的操作顺序,加载需要或注册的组件及服务引用。
在这个示例中,OrdersModule 程序集定义一个OrdersRepository 类(还有其他视图和实现订单的功能的类)。CustomerModule 程序集定义了一个CustomersViewModel 类,此类依赖OrdersRepository, 通常是用基于一个接口公开的服务。程序启动和引导过程包括以下步骤:
-
注意 用于公共OrderRespository功能的IOrderRepository接口可以单独放置一个“共享服务”的程序集或一个“订单服务”程序集,此程序集可以只包含服务接口。这样就是实现CustomersModule和OrdersModule松耦合。
- 引导器开始模块初始化过程,模块加载器加载并初始化OrdersModule.
- 在OrdersModule 初始化里,它注册OrdersRepository 到容器里。
- 然后模块加载器加载CustomersModule。加载模块的顺序可以通过在模块的元数据的依赖关系来指定。
- CustomersModule 会构造一个CustomerViewModel 实例。是通过容器解析它。CustomerViewModel 有一个OrdersRepository 依赖并通过构造器或属性注入表明它。在视图模型发生的容器注入是基于OrdersModule 已经被注册。这样的好处是两模块是松耦合,依赖的是接口。
-
注意两个模块两个模块实现依赖是靠依赖注入容器。依赖的注入发生在模块加载器的模块构造时。
核心场景
此节描述了当同模块工作时你将遭遇的通常场景。这些场景包括定义一个模块,注册并发现模块,加载模块,初始化模块,指定模块依赖,点播加载模块,后台下载远程模块,还有当一个模块已经加载时检查。你可以利用代码,XAML或程序配置文件,或通过本地路径注册和发现模块。
定义一个模块
一个模块是一系列功能和资源的集合。可以单独开发,测试,部署并集成到程序的。每个模块有一个中央类,复杂初始化模块和集成功能到程序。此类实现了IModule 接口,如下所示。
public class MyModule : IModule { public void Initialize() { // Initialize module } }
你实现initialize方法的方式将取决于您的应用程序的需求。模块类型,初始化模块,模块目录中定义的依赖项。在目录中的每个模块,模块加载器创建一个模块类实例,并调用Initialize 方法。模块在模块目录中指定的顺序进行处理。在运行时初始化顺序是根据当模块被下载,可用,满足依赖关系而定。
根据模块目录也就是应用程序正在使用的类型,模块依赖关系既可以通过设置属性声明在模块类本身或就在模块目录文件里。下章提供更多细节。
注册和发现模块
模块在模块目录中定义。Prism模块加载器使用模块目录去决定哪个模块是可用的,什么时候被加载和以什么顺序加载。
模块目录是由实现IModuleCatalog 接口的类来表示。模块目录类是在程序初始化期间有程序引导器类创建。Prism提供不同实现模块目录的方式供你选择。你可以调用AddModule方法从其它数据源填充模块目录或继承ModuleCatalog类以自定义行为创建模块目录。
注意 |
---|
通常,Prism模块使用一个依赖注入容器和公共服务定义器来取出模块初始化所需类型实例。Unity和MEF容器都被Prism支持。虽然注册,发现,下载和初始化模块的整个过程是相同的,但细节略有不同。方法之间的特定容器的差异将在本篇中说明。 |
代码注册模块
大多数基本模块目录通过ModuleCatalog 类提供。你可以使用这个模块目录编程注册模块。也可以编程指定模块名称和初始化模式。为了注册模块到ModuleCatalog 类,调用AddModule 方法在你的应用程序Bootstrapper 类。代码示例如下。
protected override void ConfigureModuleCatalog() { Type moduleCType = typeof(ModuleC); ModuleCatalog.AddModule( new ModuleInfo() { ModuleName = moduleCType.Name, ModuleType = moduleCType.AssemblyQualifiedName, }); }
在上例中,模块被直接引用到壳里。所有模块类型可被直接定义并使用在AddModule。所有利用 typeof(Module) 就可以将模块添加到目录。
注意 |
---|
如果你的应用程序有一个模块类型的直接引用,你可以通过类型添加它(如上所示);否则你需要提供类型全名和程序集位置。 |
想看代码中定义模块目录的另一个例子,看StockTraderRIBootstrapper.cs(来自股票操盘参考实现)。
注意 |
---|
Bootstrapper 基类提供了 CreateModuleCatalog 方法协助创建 ModuleCatalog. 默认情况下,此方法创建一个ModuleCatalog 实例,单此方法可以被重写,进而创建不同类型的模块目录。 |
使用一个XAML文件注册模块
你可以指定一个XAML文件来定义模块目录。XAML文件指定各种模块目录。通常xaml文件被当做一个资源添加到壳项目。目录目录通过引导器的CreateFromXaml 方法创建。从技术角度讲,此方法和在代码中定义ModuleCatalog非常相似,XAML文件只是定义了要实例化的对象的层次结构。
下面代码示例展示一个模块目录的XAML文件。
<!-- ModulesCatalog.xaml --> <Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism"> <Modularity:ModuleInfoGroup Ref="file://DirectoryModules/ModularityWithMef.Desktop.ModuleB.dll" InitializationMode="WhenAvailable"> <Modularity:ModuleInfo ModuleName="ModuleB" ModuleType="ModularityWithMef.Desktop.ModuleB, ModularityWithMef.Desktop.ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </Modularity:ModuleInfoGroup> <Modularity:ModuleInfoGroup InitializationMode="OnDemand"> <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleE.dll" ModuleName="ModuleE" ModuleType="ModularityWithMef.Desktop.ModuleE, ModularityWithMef.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleF.dll" ModuleName="ModuleF" ModuleType="ModularityWithMef.Desktop.ModuleF, ModularityWithMef.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <Modularity:ModuleInfo.DependsOn> <sys:String>ModuleE</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> </Modularity:ModuleInfoGroup> <!-- Module info without a group --> <Modularity:ModuleInfo Ref="file://DirectoryModules/ModularityWithMef.Desktop.ModuleD.dll" ModuleName="ModuleD" ModuleType="ModularityWithMef.Desktop.ModuleD, ModularityWithMef.Desktop.ModuleD, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </Modularity:ModuleCatalog>
注意 |
---|
ModuleInfoGroups 提供一个方法的方式了组织在相同程序集的模块,以相同方式初始化,或只对模块有依赖关系的分在同一个组。 模块之间的依赖关系可以在在同一ModuleInfoGroup 模块中所定义; 然而,你不能在不同的ModuleInfoGroups 定义模块之间的依赖关系。 模块内模块组是可选的。这是一组设置的属性将应用于其包含的所有模块。注意,模块也可以不作为一个组内注册。 |
在你的Bootstrapper 中,你需要指定 XAML文件作为模块目录的源,如下代码所示。
protected override IModuleCatalog CreateModuleCatalog() { return ModuleCatalog.CreateFromXaml(new Uri("/MyProject;component/ModulesCatalog.xaml", UriKind.Relative)); }
使用配置文件注册模块
在WPF中,可以把指定模块信息放在App.config 文件中,这种方法的优点文件没有被编译到应用程序。这使得它很容易在运行时添加或删除模块,而无需重新编译应用程序。
下面代码示例展示一个配置文件指定模块目录。如果你想要模块自动加载,设置 startupLoaded="true".
<!-- ModulesCatalog.xaml --> <Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism"> <Modularity:ModuleInfoGroup Ref="file://DirectoryModules/ModularityWithMef.Desktop.ModuleB.dll" InitializationMode="WhenAvailable"> <Modularity:ModuleInfo ModuleName="ModuleB" ModuleType="ModularityWithMef.Desktop.ModuleB, ModularityWithMef.Desktop.ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </Modularity:ModuleInfoGroup> <Modularity:ModuleInfoGroup InitializationMode="OnDemand"> <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleE.dll" ModuleName="ModuleE" ModuleType="ModularityWithMef.Desktop.ModuleE, ModularityWithMef.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleF.dll" ModuleName="ModuleF" ModuleType="ModularityWithMef.Desktop.ModuleF, ModularityWithMef.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <Modularity:ModuleInfo.DependsOn> <sys:String>ModuleE</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> </Modularity:ModuleInfoGroup> <!-- Module info without a group --> <Modularity:ModuleInfo Ref="file://DirectoryModules/ModularityWithMef.Desktop.ModuleD.dll" ModuleName="ModuleD" ModuleType="ModularityWithMef.Desktop.ModuleD, ModularityWithMef.Desktop.ModuleD, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </Modularity:ModuleCatalog>
注意 |
---|
即使你的程序集是在全局程序集缓存,或在同一文件夹中的应用,assemblyFile 特性还是需要的。该特性用于将moduleType 映射到正确的IModuleTypeLoader 使用。 |
在你的应用程序的 Bootstrapper 类中,你需要指定这个配置文件是ModuleCatalog的源。为做到这个,你可以使用ConfigurationModuleCatalog 类,如下代码所示。
protected override IModuleCatalog CreateModuleCatalog() { return new ConfigurationModuleCatalog(); }
注意 |
---|
你可以利用添加模块到ConfigurationModuleCatalog 中。可以把系统必须用的功能功能用代码添注册。 |
在路径上发现模块
Prism的DirectoryModuleCatalog 类允许你知道一个本地路径作为一个模块路径。这个模块目录将扫描指定的文件夹和搜索定义模块为您的应用程序集。为了用词方法,你将需要使用声明式特性在你的模块类上,指定模块名称和任何模块依赖项。下面代码示例展示一个模块目录由路径发现的程序集填充。
protected override IModuleCatalog CreateModuleCatalog() { return new DirectoryModuleCatalog() {ModulePath = @".\Modules"}; }
加载模块
ModuleCatalog 被填充之后,模块就准备好被加载和初始化了。模块加载意味着模块程序集成磁盘读入内存。ModuleManager 负责协调加载和初始化过程。
初始化模块
模块加载之后,就是初始化。这意味着模块类的实例被创建并且Initialize 方法被调用。初始化是集成模块到程序的地方。考虑模块初始化的以下可能性:
- 注册模块的视图到应用程序中。如果你的模块使用视图发现或视图注入来参与用户界面(UI )。你的模块将需要关联模块的视图或视图模块到相关的区域名称。这允许视图动态显示菜单,工具栏,或其他视觉区域。
- 订阅应用程序级别的事件或服务。经常,应用程序公开应用程序特定的服务和事件是你的模块感兴趣的。使用Initialize方法来添加模块功能来使用这些程序级别的事件或服务。
注意 模块示例的生命周期是短暂的,Initialize 方法被调用之后,如果不保存个强引用,它就被垃圾回收了。
这种行为可能会产生问题,事件定义完了,模块消失了,就不执行了。 - 依赖注入容器注册类型。如果你使用依赖注入模式如Untiy或是MEF,模块可以注册类型到容器,以便其他模块使用。模块也可以向容器解析自己需要的类型。
指定模块依赖关系
模块可能依赖于其他模块。如果模块A依赖于模块B ,模块B必须在模块A初始化之前进行初始化。ModuleManager 追踪这些依赖关系从而进行初始化模块。根据你定义的模块目录,你可以利用代码,配置文件,或XAML定义依赖关系。
用代码指定依赖关系
对于WPF应用程序,在代码中,通过路径注册模块或发现模块,Prism提供了声明特性。代码示例如下。
// (when using Unity) [Module(ModuleName = "ModuleA")] [ModuleDependency("ModuleD")] public class ModuleA: IModule { ... }
用XAML指定依赖关系
下面XAML展示模块F依赖于模块E。
<!-- ModulesCatalog.xaml --> <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleE.dll" moduleName="ModuleE" moduleType="ModularityWithMef.Desktop.ModuleE, ModularityWithMef.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <Modularity:ModuleInfo Ref="file://ModularityWithMef.Desktop.ModuleF.dll" moduleName="ModuleF" moduleType="ModularityWithMef.Desktop.ModuleF, ModularityWithMef.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" > <Modularity:ModuleInfo.DependsOn> <sys:String>ModuleE</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> . . .
在配置文件中指定依赖关系
下面的示例App.config文件展示模块F依赖于模块E
<!-- App.config --> <modules> <module assemblyFile="ModularityWithUnity.Desktop.ModuleE.dll" moduleType="ModularityWithUnity.Desktop.ModuleE, ModularityWithUnity.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleE" startupLoaded="false" /> <module assemblyFile="ModularityWithUnity.Desktop.ModuleF.dll" moduleType="ModularityWithUnity.Desktop.ModuleF, ModularityWithUnity.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleF" startupLoaded="false"> <dependencies> <dependency moduleName="ModuleE" /> </dependencies> </module> </modules>
按需加载模块
为实现按需加载模块,你需要设置它们在模块目录中的InitializationMode 属性设置为OnDemand 。做完这个操作之后,你需要写些代码来请求模块被加载。
代码中指定按需加载
一个模块设置了点播属性,如下代码所示。
// Boostrapper.cs protected override void ConfigureModuleCatalog() { . . . Type moduleCType = typeof(ModuleC); this.ModuleCatalog.AddModule(new ModuleInfo() { ModuleName = moduleCType.Name, ModuleType = moduleCType.AssemblyQualifiedName, InitializationMode = InitializationMode.OnDemand }); . . . }
XAML中指定按需加载
你可以指定 InitializationMode.OnDemand ,如下所示。
<!-- ModulesCatalog.xaml --> ... <module assemblyFile="ModularityWithUnity.Desktop.ModuleE.dll" moduleType="ModularityWithUnity.Desktop.ModuleE, ModularityWithUnity.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleE" startupLoaded="false" /> ...
在配置文件中指定按需加载
你可以指定 InitializationMode.OnDemand ,如下所示。
<!-- App.config --> <module assemblyFile="ModularityWithUnity.Desktop.ModuleC.dll" moduleType="ModularityWithUnity.Desktop.ModuleC, ModularityWithUnity.Desktop.ModuleC, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleC" startupLoaded="false" />
请求按需加载模块
在模块被指定为按需加载后,应用程序可以要求模块被加载。通过IModuleManager 服务可以做到。
private void OnLoadModuleCClick(object sender, RoutedEventArgs e) { moduleManager.LoadModule("ModuleC"); }
当模块加载后检测
ModuleManager 服务提供一个事件用来追踪模块加载完成或是失败,你可以获取一个引用通过 IModuleManager 接口的依赖注入。
this.moduleManager.LoadModuleCompleted += this.ModuleManager_LoadModuleCompleted;
void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e) { ... }
保存程序和模块之间松耦合,应用程序应该避免使用事件去集成模块到程序中,应该使用模块的Initialize方法处理集成。
LoadModuleCompletedEventArgs 包含一个IsErrorHandled 属性,如果模块加载失败,程序想阻止记录错误抛出异常,可以将此属性设成true.
注册 |
---|
模块加载和初始化之后,模块程序集不能被卸载。模块示例的引用可以不被Prism库持有,那么一会它就被垃圾回收了。 |
MEF中的模块
如果您选择使用MEF作为你的依赖注入容器,那么看看此节给出的一些差异。
注意 |
---|
当使用MEF时, MefModuleManager 使用MefBootstrapper。它扩展了ModuleManager 并实现了IPartImportsSatisfiedNotification 接口以确保 ModuleCatalog在新类型导入时更新ModuleCatalog |
在代码中使用MEF注册模块
当使用MEF,你可以允许ModuleExport 特性到模块类,MEF可自动发现类型,以下是个例子。
[ModuleExport(typeof(ModuleB), InitializationMode = InitializationMode.OnDemand)] public class ModuleB : IModule { ... }
你也可以使用MEF发现和加载模块通过使用AssemblyCatalog 类,可以发现所有在程序集中的导入模块。还有AggregateCatalog 类,允许多种目录组合成一个逻辑目录,默认情况下,PrismMefBootstrapper 创建一个 AggregateCatalog实例。你可以重写ConfigureAggregateCatalog 方法来注册程序集,如下代码所示。
protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); //Module A is referenced in in the project and directly in code. this.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(typeof(ModuleA).Assembly)); this.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(typeof(ModuleC).Assembly)); . . . }
Prism MefModuleManager 实现保存 MEF AggregateCatalog 和Prism ModuleCatalog 同步。因此允许Prism去发现模块添加使用ModuleCatalog 或AggregateCatalog.
注意 |
---|
ME使用 Lazy<T> 可以防止类型都被实例化,可以到此类型被使用时实例化。 |
使用MEF在路径下发现模块
MEF提供一个 DirectoryCatalog 可以检查路径是否包含模块程序集(或是其他MEF导出类型)。这种情况下,你重写ConfigureAggregateCatalog 方法去注册路径,此方法只在WPF中可用。
为了使用此方法,你首先需要申请模块名称和请求了ModuleExport 特性的依赖关系,如下代码示例所示。这允许MEF导入模块和允许PRism保持ModuleCatalog 更新。
protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); . . . DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules"); this.AggregateCatalog.Catalogs.Add(catalog); }
使用MEF在代码中指定依赖关系
对于使用MEF的WPF应用程序,使用ModuleExport 特性,如下所示。
// (when using MEF) [ModuleExport(typeof(ModuleA), DependsOnModuleNames = new string[] { "ModuleD" })] public class ModuleA : IModule { ... }
一个模块在ModuleCatalog 然后使用MEF加载,ModuleCatalog 依赖关系将被使用,DependsOnModuleNames 特性就会被忽略。(没太明白)
使用MEF指定按需加载
如果你可以使用MEF和ModuleExport 特性来指定模块和模块依赖,你可以使用InitializationMode 属性指定一个模块应该按需加载,如下所示。
[ModuleExport(typeof(ModuleC), InitializationMode = InitializationMode.OnDemand)] public class ModuleC : IModule { }
更多信息
更多程序集缓存信息,请看MSDN上的"How to: Use Assembly Library Caching"
学习更多Prism模块化知识,请看MEF模块化快速入门或是Untiy模块化快速入门。更多信息请看快速入门Modularity QuickStarts.
关于可扩展的Prism库的模块化功能的信息,请看在 Extending the Prism Library的 Modules 。