4 模块化

1. 背景

将应用程序分成一个一个模块是非常有好处,利于协同开发,扩展,测试,以及维护。

1.1. Prism对模块化的支持

使用Prism框架组织代码就是为了对一个个部件模块化,降低耦合度。Prism提供如下方法实现模块化。

  • 模块目录,用于注册模块信息,如名称以及模块位置。
  • 为模块声明元属性,支持模式初始化以及依赖
  • 整合依赖注入容器,支持模块间解耦
  • 在模块导入方面,实行依赖模块管理,防止重复导入。并且支持OnDemand以及Background导入。

2. 核心概念

2.1. IModule

一个模块可包含多个程序集,每个模块均需实现IModule接口,IModule是模块对外暴露部分。IModule接口很简单,只有一个Initialize方法,你可以在该方法整合需要的内容。

public class MyModule : IModule
{
    public void Initialize()
    {
        // Do something here.
    }
}

2.2. 模块导入过程

模块导入分为以下过程:

  • 注册/发现模块,将模块添加至模块目录即完成注册过程。模块目录包含许多模块信息,包括模块名称,位置,以及导入顺序。
  • 导入模块,模块中包含的所有程序集均导入内存中,这个过程可能需要从远程服务器导入或者本地目录导入。
  • 初始化模块,本阶段创建一个模块类的实例,同时调用初始化方法Initialize。

3. 模块注册

有多种方法注册模块。模块目录就是一个包含多个类ModuleInfo的集合,填充该目录。依据注册位置分为不同方法。

3.1 代码注册

在Bootstrapper文件重写方法ConfigureModuleCatalog,导入模块:

protected override void ConfigureModuleCatalog()
{
    var catalog = (ModuleCatalog)ModuleCatalog;
    catalog.AddModule(typeof(ModuleAModule));
}

可执行程序需添加对模块程序集的引用。

3.2 配置文件注册

Prism自动从配置文件读取信息,查找对应dll中的模块,示例如下:

 <!--注册信息包括dll名称,模块类名,版本,设定程序启动是否导入-->
  <modules>
    <module assemblyFile="ViewSwitchingNavigation.Contacts.dll" moduleType="ViewSwitchingNavigation.Contacts.ContactsModule, ViewSwitchingNavigation.Contacts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ContactsModule" startupLoaded="true" />
  </modules>

在bootstrapper类中重写方法CreateModuleCatalog,

protected override IModuleCatalog CreateModuleCatalog()
{
    return new ConfigurationModuleCatalog();
}

可执行程序无需添加对模块程序集的引用。

3.3 文件夹注册

在bootstrapper类中重写方法CreateModuleCatalog,使用文件夹目录,

protected override IModuleCatalog CreateModuleCatalog()
{
    return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
}

你只需要将对应的模块拷贝到目录下,上述为Modules文件夹,Prism自动从文件夹导入。可执行程序无需添加对模块程序集的引用。

3.4. 延迟导入

默认情况下模块注册后系统就导入模块程序集,通常这需要耗费一定时间。在复杂的企业应用中,并不是所有模块均需在程序启动时导入,这将导致程序启动缓慢,占用内存高等问题。模块化的应用我们可以依据需要导入模块。遵循以下原则:

  • 应用程序启动时需具备模块需要在启动时导入。
  • 应用常使用的模块可在模块被检测到时以背景方式导入和初始化。
  • 那些很少使用模块可在需要时导入。

后台代码,配置文件支持按需导入。具体实现是在注册时声明模块的初始化模式是按需导入。

protected override void ConfigureModuleCatalog()
{
    var moduleAType = typeof(ModuleAModule);
    ModuleCatalog.AddModule(new ModuleInfo()
    {
        ModuleName = moduleAType.Name,
        ModuleType = moduleAType.AssemblyQualifiedName,
        InitializationMode = InitializationMode.OnDemand
    });
}

运行时使用接口IModuleManager的方法LoadModule导入模块,可以使用依赖注入方式获取IModuleManager实例,

using Prism.Modularity;
using System.Windows;

namespace Modules.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        IModuleManager _moduleManager;

        public MainWindow(IModuleManager moduleManager)
        {
            InitializeComponent();
            _moduleManager = moduleManager;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _moduleManager.LoadModule("ModuleAModule");
        }
    }
}

4. 模块初始化

模块初始化工作一般是在前面提到的Initialize()方法中,模块初始化一般完成如下工作:

  • 注册当前模块对外提供的服务
  • 注册相应的View

如果需要在View之间转换,还需要注册相应的View,并配置用于导航,如下:

//注册硬件服务
_container.RegisterInstance(new Hardware());
//注册需要的view,这里仅注册,不会填充到相应的域中
_container.RegisterTypeForNavigation<SelectDrugView>();
//注册view到相应的域中
_regionReg.RegisterViewWithRegion(RegionName.MainRegion,typeof(MainView));

注意只要注册相应的View就自动注册与该View对应的ViewModel以及对应Popup窗口。

4.1 注意

当一个模块导入并且初始化以后,这个模块程序集不能被卸载。模块实例并不由Prism库管理,所以模块实例将在初始化完成以后进行垃圾回收。

5. 模块间交流

将应用解耦成各个模块后,为了实现某个功能,又需要多个模块协作。这就涉及模块间的交流。模块交流有很多方式。

5.1 低耦合事件

当一个模块广播事件,其他模块订阅该事件。

优点
  • 容易实现
  • 轻量级
缺点
  • 难以维护
  • 不适合多个事件组合完成一个单一任务情况

5.2 共享服务

共享服务是一个能通过统一接口访问的类,一般定义在共享程序集或者系统服务中。

优点
  • 容易维护
  • 可实现复杂交流
缺点
  • 未知

5.3 共享资源

这里的资源不是指图片,声音,文字等媒体资源,而是某种系统或者公共接口,如数据库系统,WebService集。模块与模块间不直接交流,使用资源作为中介。

graph LR
A(Module A)-->B(Resource, include WebService, database,etc...)
B-->C(Module B)

6. 关键决策

6.1. 使用的框架

你可以选择创建自己框架或者复用现有框架,如Prism,MEF及其他。

3.2. 如何组织解决方案

从逻辑角度看,定义每个模块的边界,如需要包含的程序集有哪些等是实现一个模块化架构基础。
从代码角度看,一般情况一个模块一个程序集,对应到代码中则是一个模块一个Project。但是对于大型项目,可能存在10~50个模块,每个模块一个项目会拖慢visual studio。这种情况就需要考虑一个项目是否应该对应多个模块。

3.3. 如何分解模块

可以依据需求将系统分解为不同模块,例如通过功能域分解(设计常用),依据模块供应商分解,依据开发团队分解,依据部署需求分解。你可以从水平功能块分解或者垂直服务分解。水平是指从商业功能方向分解,如一个交易系统包括用户模块,产品模块,订单模块。垂直分解则是从技术角度分解,如拦截业务模块(日志,缓存),商业功能块,UI模块等。
好模块特征:

  • 功能单一
  • 对其他模块具有最小依赖
  • 以接口方式与其他模块交流,该接口定义在共享类库中,或者使用EventAggregator与其他模块交流。

7. 问题

模块化时不同模块的View是否可以重名?

posted @ 2020-11-01 23:16  饮冰少年  阅读(111)  评论(0编辑  收藏  举报