65.9K
CodeProject 正在变化。 阅读更多。
Home

一个轻量级的 .NET 组件框架,仅用 100 行代码实现

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.53/5 (12投票s)

2006 年 4 月 26 日

5分钟阅读

viewsIcon

56681

downloadIcon

155

一种用于 n 层组件模型设计和实现的极简方法,为应用程序模块的功能分离提供了极大的灵活性。

引言

为事物、事件或某个抽象找到一个恰当的名称总是令人着迷的。有时,词语首先以思想公式的形式出现,然后我们开始寻找将其具体化的方法。设计模式的隐喻是你组织周围世界(分析)的方式,同时也是你创造新世界(综合)的方式,词语创造世界。

关于类工厂,最容易令人误解和隐藏的事情之一是如何阐明通过其他类对象创建类实例的思想。这样做的唯一原因应该是通过它们整个生命周期更好地控制这些新实例(可以创建多少实例、安全限制、对象所有权等),以及更好地解耦组件实现与组件使用者。

关于组件模型,另一个重要的考虑因素是提供从组件实例获取当前状态信息的可能性,并异步运行其中一个方法。用爱丽丝的话来说,一个没有异步回调的组件有什么用?

在本文中,我们将讨论一些构造,我们称之为组件工厂,这个名称更好地解释了应用程序架构层次解耦的本质。

分解与设计

我们将提出 3 层模型

  • 主应用程序或使用者;
  • 实现我们组件创建策略的中间层;
  • 包含不同组件(插件)声明的业务逻辑层。

所有实现层都将驻留在不同的程序集中。

我们要做的第一件事是描述一个插件接口。我们选择 IPlugin 这个名字是为了表明我们面向组件的方法。

此时,我们将提供一种方式来调用同步和异步的插件方法。接口上只有两个方法可以启动业务逻辑例程:DoDoAsync,分别执行同步和异步调用。它们都只有一个参数 - 一个字符串,包含任何表示形式的调用参数,例如 XML。这个参数字符串随后会被解析,业务类会被实例化,并调用相应的方法。

换句话说,插件充当重定向器,解析请求并将数据流式传输到相应的业务模块。插件本身以及业务模块将构成业务逻辑层。

为了引入异步调用,我们需要想出一个方法来通知应用程序业务逻辑计算的状态。我们将在新线程上启动异步操作。每当我们想向使用者通报重要事项时,我们都会从插件中触发一个事件,该事件应该在使用者应用程序中被捕获。我们将基于通过反射 API 发现的插件元数据应用事件连接。

现在,我们准备好设计一个具有某种创建策略的组件工厂。我们将使这个类工厂成为接口库不可或缺的一部分。这将很好地解耦业务逻辑和负责业务逻辑创建和控制的抽象中间层,从而形成我们自己的全功能组件模型。我们将定义 CreateObjectDirect 方法,负责加载插件程序集并调用测试插件类中的静态 Create 方法,该方法将实际实例化一个新的插件实例(极晚绑定)。CreateObjectDirect 方法还将把插件中产生的事件连接到使用者应用程序的回调方法。

概念验证

我们的解决方案将包含三个项目,其中包含下面列出的程序模块:ComponentFactoryTest (ComponentFactoryTest.cs) - 包含使用者源代码;PluginInterface (PluginInterface.cs) - 接口、事件类、事件委托;ComponentFactory.cs - 组件工厂逻辑;TestPluginImplementation (TestPlugin.cs) - 插件重定向器;TestBusinessLogic.cs - 无用户界面的示例业务模块;TestBusinessForm.cs - 基于 Windows 窗体的示例业务模块。

IPlugin 接口相当简单

public interface IPlugin
{
    int    Mode{ get; set; }
    void   Setup(string p_init);
    string Do( string p_XMLRequest );
    string DoAsync( string p_XMLRequest );
    bool   IsEventInitialized();
    string GetProductDetails();
    void   FireEvent(CEventArgs p_event);
    event  ProviderEventDelegate FireProviderEvent;
}

在类工厂实现中,CreateObjectDirect 方法实际上充当了使用者和插件之间的联络人。展示代码很有意义

public Object CreateObjectDirect(string p_Assembly, 
              string p_ClassName, string p_InitData)
{
    IPlugin plugin = null;
    try
    {
        Assembly assembly = Assembly.LoadFrom(p_Assembly);

        Type tPlugin =  assembly.GetType(p_ClassName);
        if(null == tPlugin)
        {
            string info = "GetType error for " + p_ClassName;
            Console.WriteLine(info);
            return null;
        }
                
        object[] arguments = new object [] {p_InitData};

        // this is the way we call static Create method 

        // of the plugin implementaion

        plugin = (IPlugin)tPlugin.InvokeMember ("Create", 
            BindingFlags.Default | BindingFlags.InvokeMethod,
            null,
            null,
            arguments);

        if(plugin.IsEventInitialized())
        {
            return plugin;
        }

        Delegate eventDelegate = 
            Delegate.CreateDelegate(typeof(ProviderEventDelegate), 
            m_that, 
            "On_ProviderEvent");

        plugin.FireProviderEvent += (ProviderEventDelegate)eventDelegate;

    }
    catch(Exception e)
    {
        string info = ">> Exception in CreateObjectDirect : " + e.Message;
        Console.WriteLine(info);
    }

    return plugin;
}

从消费者的角度来看,调用序列包括以下步骤

  1. 定义一个回调方法来捕获插件的事件;
  2. 创建组件工厂;
  3. 通过 CreateObjectDirect 调用获取新插件实例的接口,传递引用插件程序集动态库的参数;
  4. 在此接口上调用 DoDoAsync,提供调用业务逻辑方法所需的参数;
  5. 通过定义的 callback 方法接收异步调用的状态信息。

这是一个序列图,概述了使用者、组件工厂和插件的交互。

Sample image

请注意,我们在专用线程上运行业务模块实例的 Execute 方法。

测试应用程序实例化两种类型的业务逻辑插件 - 有用户界面和无用户界面的。两种插件都通过生成事件来模拟一些业务逻辑行为。要从基于用户界面的插件触发事件,只需按一下按钮。这些事件被消费者应用程序捕获,然后打印事件的详细信息。就是这样!

实现细节和注意事项

为了使 IPLugin 接口对消费者和业务逻辑模块都可访问,我们应该在一个单独的库中声明它,并在所有项目中引用它。

我们不会用强名称签署插件程序集。我们宁愿将程序集放在测试应用程序可执行文件的同一文件夹中。如果您想更改实现代码,请不要忘记这样做。

在我们的方法中,我们不会过多关注实际的调用参数操作。我们将使事情尽可能清晰,在客户端和组件代码之间实现字符串传输。我们甚至不会在意它是一个 XML 字符串。任何参数字符串格式的语义都可以是合理且方便的。这种方法有其自身的优点和缺点,并且可能在许多应用程序场景中非常有效。

结论

我们讨论了一个轻量级的 .NET 组件框架,它将帮助我们开发 n 层、方便解耦的应用程序。这种设计为未来的增强和改进提供了足够的灵活性。从进化的角度来看,我们可以开始考虑一个容器,用于保存新创建的插件实例,向 IPlugin 接口添加引用计数,提供对容器的远程访问,序列化输入/输出数据等。

听起来很熟悉,不是吗?

历史

  • 2006 年 1 月 5 日 - 感谢提交的评论。IPlugin 接口现在包括 FireProviderEvent 定义,这改进了 CreateObjectDirect 代码。添加了 CreateObjectWithActivator 方法,只是为了说明其他可能性。请注意,此函数调用要求插件具有公共构造函数,这可能会改变创建逻辑设计。
© . All rights reserved.