使用“管道”与“应用程序生命周期”重构:可插拔模块
本篇博客依然用于总结工作中遇到的较有用的设计模式。
入正题。
历史代码
我目前开发的系统中,要实现以模块的方式进行动态扩展。这些模块是以独立程序集的方式嵌入到系统中。原系统中,使用了一个简单的接口 IModule 来实现模块的初始化:
public interface IModule
{
void Initialize();
}
这样,在应用程序初始化时,会检测指定目录 Modules 下的所有程序集,并对其中所有实现 IModule 接口的类型进行初始化调用:
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 接口不断变大。这样,不但得到了一个“胖接口”,而且还破坏了接口的稳定性。
接下来,看一看我们最终采用的方案:
新的设计
重构方案如下,先在底层定义以下接口,表示应用程序的生命周期事件:
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 { }
}
接下来,修改模块初始化接口的代码:
/// <summary>
/// 模块初始化器
/// </summary>
public interface IModule
{
/// <summary>
/// 两个职责:
/// 1.某个模块的初始化工作
/// 2.注册 app 的一些事件,进行额外的初始化
/// </summary>
/// <param name="app"></param>
void Initialize(IClientApp app);
}
界面层的应用程序类,实现 IClientApp 中所定义的事件:
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 的事件,例如:
[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 接口,其代码如下:
public interface IHttpModule
{
void Dispose();
void Init(HttpApplication context);
}
也就是说,通过这样一个接口,我们就可以对 ASP.NET 应用程序进行各种扩展。例如,在《Walkthrough: Creating and Registering a Custom HTTP Module》中的示例 HttpModule 代码。在示例中可以看出,我们可以在 Init 接口实现中,监听并进行处理 HttpContext 生命周期各阶段事件,以达到各阶段代码的扩展。
是不是和之前的代码非常类似? :)
结束语
本次的重构,是一种常用的设计模式。它类似于管道与过滤器,但是又不尽相同。它首先定义了整个应用程序的动态运行架构(生命周期);开始运行时,首先动态插入多个独立模块;各模块中再次在应用程序各阶段插入执行代码(监听并处理生命周期各事件);最终实现高灵活度的模块扩展方案。
- linux下的缓存机制及清理buffer/cache/swap的方法梳理
- 分组合计且排序和显示名称
- silverlight动态读取txt文件/解析json数据/调用wcf示例
- Junit加载Spring容器作单元测试_添加事务回滚
- 实现三遍决策树,你就会想出更快的算法!
- 将一段复杂文本变成字符串的赋值语句
- Linux下squid代理缓存服务环境部署
- linux下清除Squid缓存的方法记录
- memcached缓存知识简单梳理
- Idea 常用快捷键
- silverlight中如何方便在多个"场景"即Xaml文件之间随意切换?
- 电子签名实现的思路、困难及解决方案
- JavaScript排序算法详解
- 事件处理需小心
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- kubernetes(二十一) 微服务链路监控& 自动发布
- kubernetes(八) kubernetes的使用
- kubernetes(九) kubernetes控制器
- kubernetes(十) kubernetes service,ingress&cm,secret
- kubernetes(十一) 存储& statefulset控制器
- kubernetes(十二) 准入控制和helm v3包管理
- JS Flowchart Diagrams
- kubernetes(十三) k8s 业务上线流程(手动版)
- java+appium+安卓模拟器实现app自动化Demo
- webdriver使用已打开过的chrome
- Shortcodes
- Unexpected EOF 远程主机强迫关闭了一个现有的连接 如何处理
- npm 使用问题
- 接口自动化测试框架-AIM
- hexo 图片显示问题及使用typora设置图片路径