现在的位置: 首页 > 综合 > 正文

标签式用户界面的故事续集

2013年07月13日 ⁄ 综合 ⁄ 共 2150字 ⁄ 字号 评论关闭

一年前的标签式用户界面的故事,试图从一个GUI的问题引出一个关于插件体系的讨论。一年后,发现自己有幸再次以插件模型为基础搭建了又一个软件系统的静态结构。这种模型已是如此的普遍,从网络游戏到领域应用框架,代码的动态加载为系统架构带来的灵活性是如此的显而易见,以至稍微上点规模的软件都离不开它。

在代码实现上,.Net提供了非常方便的方法。除了用前文提到的用静态函数Assembly.Load()把程序集(Assembly)加载到当前默认的应用程序域(AppDomain)的方法以外,还可以在程序中动态创建一个新的AppDomain,然后把程序集加载到这个新的AppDomain中。后者的好处就是在你不需要这个程序集的时候,还也可以动态卸载掉它。这也是最近才在资料上看到的,因为以前似乎还没有某种理由要求我们必须从一个进程中动态地卸载一段程序代码,但我相信以后会用到的,比如你要自己写代码来更新或是微重启一个7X24小时运行的服务器上的一些小模块,而且设计的时候这些模块的粒度已经小到无法用进程作为边界来划分了。

这是我们的插件接口:

public interface ISomething
{
    
string DoSomething(string sourceAppDomain);
}

这是宿主的实现:

AppDomain ad = AppDomain.CreateDomain("MyNewAppDomain"nullnull);
ISomething obj 
= ad.CreateInstanceAndUnwrap("SomeAssembly","SomeAssembly.MyObject"as ISomething;
MessageBox.Show(obj.DoSomething(Thread.GetDomain().FriendlyName));
AppDomain.Unload(ad);

这是插件的实现,比如被编译成SomeAssembly.dll。

namespace SomeAssembly
{
    
public class MyObject : MarshalByRefObject, ISomething
    
{
        
public string DoSomething(string sourceAppDomain)
        
{
            
return string.Format("call from {0} to {1}", sourceAppDomain, Thread.GetDomain().FriendlyName);
        }

    }

}

跟Remoting的机制一样,所有穿过AppDomain的东西都要序列化。你可以在类的定义中加上Serializable这个Attribute,这个类实例在穿过AppDomain的边界时就会被复制一份。如果换一种方式,你让类派生于MarshalByRefObject,那么它的实例就不会真正穿过AppDomain的边界,而是在另外一端建立一个代理,因此在上面的例子中才会看到两个地方调用Thread.GetDomain().FriendlyName返回的结果不一样。这两种方式在语法上看是正交的,但MarshalByRefObject其实也声明成了Serializable,因此如果两种方式同时存在的话,.Net会以后者为先。

需要注意的是,虽然在两个地方调用Thread.GetDomain().FriendlyName返回的结果不一样,但两个地方调用Thread.CurrentThread.ManagedThreadId的结果还是一样的,也就是说不同AppDomain不同程序集中的代码如今已运行在同一个线程中。如果运行的时候,另一个线程把插件卸载掉了,那么当前运行的线程就会抛出异常。

---

衡量一个插件系统的好坏,接口的设计是个关键。它必须承载插件和宿主间的所有交互,但又不能太复杂,毕竟越复杂的东西越难以适应变化。这方面ASP.Net的设计似乎能给人不少启发。比如它在某些接口中只定义了一个简单的方法,这个方法的参数被定义成另外一个接口的类型,在这个被作为参数传递的接口中提供众多其他的方法和事件,来实现插件和宿主间的双向通信。不知道有没有人把这样的方式也命名成某种设计模式,然后再用深奥的术语来诠释它,让它看起来是多么的巧妙。然而,最终程序员使用这样的接口来开发插件之前,还是必须从厚厚的文档中找到关于接口中的这堆方法和事件调用顺序的描述,就像WinForm开发人员必须知道Form Layout事件总是早于Form Load事件被触发的一样。不知道有没有什么办法可以让接口定义或者调用契约中包含这些时序逻辑。

---

最近好像形成了一种思维惯性,每分析一个系统的时候总是喜欢提取出其中的几个关键要素来,然后摆弄这些要素的抽象定义,杜撰它们之间的八卦关系,于是可以装作很高深的样子,就像GoF那样。下面总结一下插件体系,其实没有什么新意,所以也懒得画图了,如果要画的话,应该跟软件设计中司空见惯的夹心饼干图差不多。

宿主:提供公共服务
插件:实现业务逻辑
基础:接口定义

抱歉!评论已关闭.