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

入正题。

历史代码


我目前开发的系统中,要实现以模块的方式进行动态扩展。这些模块是以独立程序集的方式嵌入到系统中。原系统中,使用了一个简单的接口 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 生命周期各阶段事件,以达到各阶段代码的扩展。

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

结束语


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

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

  1. IIS 7.0的ASP.NET应用程序生命周期概述

    小结于:http://msdn.microsoft.com/zh-cn/library/bb470252(v=vs.100).aspx IIS 7.0的ASP.NET应用程序生命周期概述 (一)结构概 ...

  2. IIS 7.0 的 ASP.NET 应用程序生命周期概述

    本主题介绍在 IIS 7.0 集成模式下运行以及与 .NET Framework 3.0 或更高版本一起运行的 ASP.NET 应用程序的应用程序生命周期.IIS 7.0 还支持经典模式,其行为类似于 ...

  3. 【转】Asp.net的生命周期之应用程序生命周期

    参考:http://msdn.microsoft.com/zh-cn/library/ms178473(v=vs.100).aspx 参考:http://www.cnblogs.com/JimmyZh ...

  4. Asp.net的生命周期之应用程序生命周期

    参考:http://msdn.microsoft.com/zh-cn/library/ms178473(v=vs.100).aspx 参考:http://www.cnblogs.com/JimmyZh ...

  5. ASP.NET 应用程序生命周期概述

    本主题概述应用程序生命周期,列出重要的生命周期事件,并描述如何编写适合应用程序生命周期的代码.在 ASP.NET 中,若要对 ASP.NET 应用程序进行初始化并使它处理请求,必须执行一些处理步骤.此 ...

  6. IIS 5.0 和 6.0 的 ASP.NET 应用程序生命周期

    本文内容 应用程序生命周期概述 生命周期事件和 Global.asax 文件 编译生命周期 HTTP 模块 本文概述 VS 2008 ASP.NET 应用程序的生命周期,列出了重要的生命周期事件,并描 ...

  7. Windows Phone 应用程序生命周期

    下图演示了 Windows Phone 应用程序的生命周期.在该图中,圆圈表示应用程序的状态.矩形显示应用程序应管理其状态的应用程序级别或页面级别的事件. Launching 事件 Launching ...

  8. ASP.NET页面生命周期与应用程序生命周期

    页面生命周期 页面生命周期执行一系列步骤:页面的初始化.实例化控件.还原和维护状态.运行事件处理程序代码.呈现.为了在合适的阶段执行所需的代码,所以要对页面生命周期非常熟悉.在页生命周期的各个阶段,页 ...

  9. IIS 5.0 和 6.0 的 ASP.NET 应用程序生命周期概述

    http://msdn.microsoft.com/zh-cn/library/ms178473(v=VS.100).aspx 在 ASP.NET 中,若要对 ASP.NET 应用程序进行初始化并使它 ...

最新文章

  1. nginx alias php,Nginx Alias 无法解析PHP的解决办法
  2. Redhat 图形模式与命令行模式的切换
  3. pxe和kickstart无人值守安装
  4. python利器怎么编程-python 开发利器UliPad(图文详细介绍)
  5. android 设置窗口透明效果,android - 如何将对话框窗口背景设置为透明,而不影响其边距...
  6. 寻宝天行服务器维护中,你好。我的电脑寻宝天行网站上不去,其他网址都可以上,都好几天了。要么就无访问,要么跳出个502什么的...
  7. 小程序和app用什么样的服务器,小程序和APP的本质区别是什么?哪个更值得开发?...
  8. Windows Mobile 模拟器网络连接设置
  9. u盘iso安装服务器系统怎么安装win7系统安装方法,win7 iso,手把手教你U盘如何安装win7系统...
  10. WCF学习资源收集汇总
  11. NickLee UI中间件 for asp.net 2.0版本
  12. vue父子组件的传值
  13. python tornado websocket_Python:Tornado 第三章:WebSocket概念及应用:第一节:WebSocket概念...
  14. C/C++创建服务器和客户端的demo
  15. html产生圆点列表符号的列表,HTML 测验
  16. java实现Calendar求两个日期差
  17. 有关计算机的英语诗歌带翻译,经典的著名英语短诗歌带翻译
  18. Web开发了一个完整精美的聊天室(登录部分引入了Vue技术)
  19. 2017前端精品面试文章总结
  20. vue项目中出现重复点击路由报错

热门文章

  1. 动态规划之0-1背包问题
  2. mysql更换主键遇到的一个问题
  3. 光驱安装centos7系统过程_centos7可以ping通外网_可以ping通内网其他机器_但是其他机器就是ping不通centos7_太神奇了---linux工作笔记041
  4. axios介绍---axios工作笔记001
  5. Sentinel热点Key降级上_分布式系统集群限流_线程数隔离_削峰填谷_流量控制_速率控制_服务熔断_服务降级---微服务升级_SpringCloud Alibaba工作笔记0042
  6. SpringCloud工作笔记075---SpotBugs介绍--优化java代码的质量
  7. tomcat 无端口号访问
  8. 仿站小技巧20190409
  9. C语言和设计模式(中介者模式)
  10. 想成为编程高手,从基础做起