使用“管道”与“应用程序生命周期”重构:可插拔模块

简介:

  本篇博客依然用于总结工作中遇到的较有用的设计模式。

    入正题。

 

历史代码


 

    我目前开发的系统中,要实现以模块的方式进行动态扩展。这些模块是以独立程序集的方式嵌入到系统中。原系统中,使用了一个简单的接口 IModule 来实现模块的初始化:

1
2
3
4
public  interface  IModule
{
     void  Initialize();
}

这样,在应用程序初始化时,会检测指定目录 Modules 下的所有程序集,并对其中所有实现 IModule 接口的类型进行初始化调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public  partial  class  App : Application
{
     protected  override  void  OnStartup(StartupEventArgs e)
     {
         base .OnStartup(e);
 
         var  modules = this .GetAllModules();
         foreach  (IModule module in  modules)
         {
             module.Initialize();
         }
     }
}

 

    这样的方案在早期可以满足一定的需求。但是随着应用程序的逐渐膨胀,越来越多、越来越细的需求,这样的初始化工作已经不能胜任。例如,某个插入的程序集,不仅需要在应用程序初始化时做一些操作,还需要在所有 Module 加载完成后再做一些更多的特殊任务。此时,这样的接口设计已经不能实现这个需求,所以我们需要重构原有的设计,添加新的功能。

    可能您的第一个想法,是在 IModule 接口中加入新的方法,如 ModulesInitialized() ,然后在 foreach 循环结束后再次调用。可是随着需求越来越多,会导致 IModule 接口不断变大。这样,不但得到了一个“胖接口”,而且还破坏了接口的稳定性。

    接下来,看一看我们最终采用的方案:

 

 

新的设计


 

    重构方案如下,先在底层定义以下接口,表示应用程序的生命周期事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
namespace  OEA
{
     /// <summary>
     /// 应用程序生成周期定义
     /// </summary>
     public  interface  IApp
     {
         /// <summary>
         /// 依赖注入完成
         /// 这里是应用程序的入口开始
         /// </summary>
         event  EventHandler DICompleted;
 
         /// <summary>
         /// 所有实体类初始化完成
         /// </summary>
         event  EventHandler LibrariesInitialized;
 
         /// <summary>
         /// 所有初始化工作完成
         /// </summary>
         event  EventHandler InitializeCompleted;
 
         /// <summary>
         /// 应用程序完全退出
         /// </summary>
         event  EventHandler Exit;
     }
 
     /// <summary>
     /// 客户端应用程序生命周期定义
     /// </summary>
     public  interface  IClientApp : IApp
     {
         /// <summary>
         /// 界面组合完成
         /// </summary>
         event  EventHandler Composed;
 
         /// <summary>
         /// 各模块初始化完成
         /// </summary>
         event  EventHandler ModulesIntialized;
 
         /// <summary>
         /// 客户化信息初始化完成
         /// </summary>
         event  EventHandler AppDefinitionIntialized;
 
         /// <summary>
         /// 登录成功,主窗口开始显示
         /// </summary>
         event  EventHandler LoginSuccessed;
 
         /// <summary>
         /// 登录失败,准备退出
         /// </summary>
         event  EventHandler LoginFailed;
     }
 
     /// <summary>
     /// 服务端应用程序生命周期定义
     /// </summary>
     public  interface  IServerApp : IApp { }
}

 

接下来,修改模块初始化接口的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/// <summary>
/// 模块初始化器
/// </summary>
public  interface  IModule
{
     /// <summary>
     /// 两个职责:
     /// 1.某个模块的初始化工作
     /// 2.注册 app 的一些事件,进行额外的初始化
     /// </summary>
     /// <param name="app"></param>
     void  Initialize(IClientApp app);
}

 

界面层的应用程序类,实现 IClientApp 中所定义的事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public  partial  class  App : Application, IClientApp
{
     protected  override  void  OnStartup(StartupEventArgs e)
     {
         this .InitCultureInfo();
 
         this .InjectDependency();
         this .ModifyPrivateBinPath();
         this .OnDICompleted();
 
         this .InitializeLibraries();
         this .OnLibrariesInitialized();
 
         this .Compose();
         this .OnComposed();
 
         this .InitializeModules();
         this .OnModulesIntialized();
 
         this .InitAppDefinition();
         this .OnAppDefinitionIntialized();
 
         if  ( this .TryLogin())
         {
             this .OnLoginSuccessed();
 
             base .OnStartup(e);
 
             this .ShowSplashScreen();
 
             this .ShowMainWindow();
 
             this .SetupAutomaticGC();
         }
         else
         {
             this .OnLoginFailed();
 
             this .Shutdown();
         }
 
         this .OnInitializeCompleted();
     }
 
     protected  override  void  OnExit(ExitEventArgs e)
     {
         base .OnExit(e);
 
         this .OnExit();
     }
 
     #region IClientApp 的事件
 
     public  event  EventHandler DICompleted;
 
     protected  virtual  void  OnDICompleted()
     {
         var  handler = this .DICompleted;
         if  (handler != null ) handler( this , EventArgs.Empty);
     }
 
     //其它事件...........

 

以上代码实现并触发应用程序的整个生命周期各事件。

那么各模块扩展的代码如何编写呢?我们需要在 IModule 的 Initialize 方法中,监听并处理 IClientApp 的事件,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Export( typeof (IModule))]
public  class  GIX4Module : IModule
{
     public  void  Initialize(IClientApp app)
     {
         this .InitCache(app);
     }
 
     private  void  InitCache(IClientApp app)
     {
         app.LoginSuccessed += (o, e) =>
         {
             //Define cache
             //Async caching.
         };
     }
}

 

这样,就实现了在登录成功后,进行缓存的定义和初始化。

    其实,这样的编写模式在.NET框架中随处可见。接下来,我将以 ASP.NET 应用程序开发为例,来分析一下在它里面,是如何进行模块化的扩展的。

 

 

ASP.NET HttpModule 及 管道模式


 

    在一般的 ASP.NET 程序设计中,我们一般可以通过 HttpModule 和 HttpHandler 来进行扩展(相关内容,可参见《HTTP Handlers and HTTP Modules Overview》及《ASP.NET Application Life Cycle Overview》)。自定义的 HttpModule 模块,都需要实现 IHttpModule 接口,其代码如下:

1
2
3
4
5
public  interface  IHttpModule
{
     void  Dispose();
     void  Init(HttpApplication context);
}

也就是说,通过这样一个接口,我们就可以对 ASP.NET 应用程序进行各种扩展。例如,在《Walkthrough: Creating and Registering a Custom HTTP Module》中的示例 HttpModule 代码。在示例中可以看出,我们可以在 Init 接口实现中,监听并进行处理 HttpContext 生命周期各阶段事件,以达到各阶段代码的扩展。

    是不是和之前的代码非常类似? :)

 

 

结束语


 

    本次的重构,是一种常用的设计模式。它类似于管道与过滤器,但是又不尽相同。它首先定义了整个应用程序的动态运行架构(生命周期);开始运行时,首先动态插入多个独立模块;各模块中再次在应用程序各阶段插入执行代码(监听并处理生命周期各事件);最终实现高灵活度的模块扩展方案。

目录
相关文章
|
3月前
|
移动开发 前端开发 weex
Android项目架构设计问题之模块化后调用式通信如何解决
Android项目架构设计问题之模块化后调用式通信如何解决
16 0
|
5月前
|
JavaScript 前端开发
JavaScript模块化将复杂软件分解为独立模块,提高代码可读、维护、复用和扩展性。
【6月更文挑战第27天】模块化将复杂软件分解为独立模块,提高代码可读、维护、复用和扩展性。JavaScript模块化有CommonJS(Node.js,`require()`/`module.exports`)、AMD(RequireJS,异步,`define()`/`require()`)和ES6 Modules(官方标准,`import`/`export`)。打包工具如Webpack、Rollup处理兼容性,使模块能在不同环境中运行。
32 0
|
6月前
|
缓存 监控 中间件
中间件应用程序数据处理逻辑
【5月更文挑战第13天】中间件应用程序数据处理逻辑
55 3
|
存储 架构师 算法
架构设计的本质:系统与子系统、模块与组件、框架与架构
在软件研发这个领域,程序员的终极目标都是想成为一名合格的架构师。然而梦想很美好,但现实却很曲折。
架构设计的本质:系统与子系统、模块与组件、框架与架构
|
JavaScript 小程序
UniApp 小程序封装原生组件(使用与交互详细流程)
UniApp 小程序封装原生组件(使用与交互详细流程)
417 0
|
JavaScript 前端开发 API
ReactJS 101:构建可重用组件、管理状态和创建实际应用程序的初学者指南
ReactJS 101:构建可重用组件、管理状态和创建实际应用程序的初学者指南
158 0
|
Android开发 Java
[架构设计] 组件和模块的区别
组件(Component)和模块(Module)又是一对容易混淆的名词,也常常被用来相互替换。两者是否有差异往往取决专业背景、所在领域、以及视角。个人总结,从设计上来看,组件强调复用,模块强调职责(内聚、分离),或者说组件是达到可复用要求的模块。
3039 0
|
iOS开发 容器
AppleWatch开发入门三——代码交互与控制器生命周期
AppleWatch开发入门三——代码交互与控制器生命周期
157 0
AppleWatch开发入门三——代码交互与控制器生命周期
|
存储 搜索推荐 Java
大中台模式下如何构建复杂业务核心状态机组件
大中台战略下,中台将公司业务的公共能力下沉,并采用更加合理、可复用的架构和技术来实现这些基础能力。在电商行业内,将面临货物的采购、商品上架、交易发生、订单状态变化、客服介入等大量状态维护。
大中台模式下如何构建复杂业务核心状态机组件
|
Cloud Native JavaScript Serverless
开发函数计算的正确姿势——使用交互模式安装依赖
函数计算以 Zip 压缩文件格式作为约定的交付物,交付物通常包含代码和依赖库文件。这些依赖库文件通常分为系统依赖(使用 apt-get 包管理安装的库)和语言运行时依赖(使用语言相关的包管理器如 npm、pip 安装的库)。安装这些依赖库时开发者需要一个交互式的沙箱环境一便于了解:已经安装了哪些软件,某个目录下有些什么文件 以及文件的内容以及属性是什么。
开发函数计算的正确姿势——使用交互模式安装依赖