插件系统概述
普通的系统,在编译发布之后,系统就不允许进行更改或扩充了,如果要进行某个功能的扩充,则必须要修改代码重新编译发布。使用插件可以很好地解决这个问题。
插件概念
首先由开发人员编写系统框架,并预先定义好系统的扩展借口。插件由其他开发人员根据系统预定的接口编写的扩展功能,实际上就是系统的扩展功能模块。插件都是以一个独立文件的形式出现。
对于系统来说并不知道插件的具体功能,仅仅是为插件留下预定的接口,系统启动的时候根据插件的配置寻找插件,根据预定的接口把插件挂接到系统中。
优势
一、系统的扩展性大大地加强了。如果我们在系统发布后需要对系统进行扩充,就不必重新编译,只需要增加或修改插件就可以了。
二、有利于模块化的开发方式。我们可以开发强大的插件管理系统,在这样的一个插件系统下,我们可以不修改基本系统,仅仅使用插件就能构造出各种各样不同的系统。
Eclipse系统架构
Eclipse插件系统是非常成功的插件框架结构。网上有很多介绍的文章。这里推荐孟岩的Blog http://www.mengyan.org/blog/archives/2005/09/08/67.html。下面对Eclipse的框架中的几点做一个简要的介绍,在后面介绍插件系统架构的时候作为对比。
插件结构
Eclipse是众多“可供插入的地方”(扩展点)和“可以插入的东西”(扩展)共同组成的集合体。在我们的生活中,电源接线板就是一种“扩展点”,很多“扩展”(也就是电线插头)可以插在它上面。(摘自《Contributing to Eclipse》 Erich Gamma, Kent Beck著)
Eclipse整个IDE就是一个插件,他提供了新的扩展点供其他插件来扩展。
扩展点
可以看到Eclipse的插件结构是由父插件管理子插件,插件之间由扩展点连接,最终形成树形的结构。
界面呈现
界面呈现由提供扩展点的父插件来决定,比如说父插件在菜单上留了扩展点,那么子插件就可以出现在菜单项上。界面呈现的类型是由提供扩展的插件决定。
插件交互
插件之间的交互通过扩展点实现。父插件调用子插件实现的扩展点来触发子插件的动作。
依赖关系
配置文件中指定插件运行需要依赖的插件,在装载过程中会按照依赖的关系顺序来装载。
扩展点形成的系统结构
Eclipse中的插件用扩展点的机制连接起来,形成如下图所示的系统结构。插件必须实现扩展点,以此插入到系统中,新增扩展点并不是必须的,但只有新增了扩展点的插件才可以被别人扩展。
懒加载
只有在调用执行动作的时候才会将真实的动作对象创建起来。由于在配置文件中已经具备真实动作的一切信息,所以在不装载插件时,同样可以在父插件的界面上将扩展的功能显示出来。
另一个插件系统
插件结构
插件分为“插件外壳”和“业务”两部分。
其中业务部分与插件没有任何关系,按照一般的应用程序开发即可。最终提供给插件外壳一个主要的界面和公布出来的方法。
插件外壳提供接口供外界调用。系统和其它插件完全通过插件外壳和插件进行交互。
界面呈现
将每个插件的界面按照一定形式组织起来生成整个系统。界面组织的规则在配置文件中指定。系统提供可配置的方案。
布局(Layout)
插件按照一定的布局放到整个系统的界面中。在目前的系统内提供了三种布局。
页面布局
将插件按照页面的形式重叠在一起,插件激活时将自己所属的页面翻转到最前端。
模块布局
将插件按照模块划分放到同一个界面显示。模块之间用分割条连接。
页签布局
将插件按照页签的形式放到一起。
装饰(Decorator)
布局指定了插件出现的位置与形式。装饰可以指定插件出现的方式。
可关闭装饰
指定插件出现的部分是否可以关闭。在普通的模式下,插件可以按照上面的几种版型出现,但这时的插件界面是不可关闭的。如果需要增加关闭功能,可以给插件指定一个装饰器。
下面举一个在模块布局中的模块2上应用“可关闭装饰”的例子。
布局、装饰的组合
上面列举了现有的布局与装饰,复杂界面同样可以有布局与装饰的组合来完成。这里的图式表明将三种布局与装饰组合的一种情况。
通过配置文件指定出不同的组合情况就可以完成更多的界面布局了。在更改整个系统界面布局的时候只需要修改配置文件,程序并不需要重新发布。
导航
通过配置文件装配好的插件系统,界面可能是非常复杂的。这种情况下要让用户找到想要的功能需要用导航器来呈现系统提供的所用功能。
系统提供的功能就是插件提供的功能的集合,插件提供的功能通过插件外壳公布出来。公布的方式依照语言的特性来定:C#、Java中可以利用反射机制运行公布出来的方法,Delphi中用RTTI也可以同样运行配置文件中指定的方法。
常见的导航器都可以抽象成树形结构。每一个导航单元映射到一个用户需要的功能,每一个功能对应到具体的插件的某一个方法。将功能抽象成一个Action对象,对象需要知道它导向的插件和方法名。
可以在上面抽象模型的基础上实现任意形式的导航器。可以是菜单项,可以是TreeView,也可以是自定义的控件。
交互关系
系统需要知道插件的操作,插件与插件之间同样也会有交互。
将所有的交互关系用一个关系管理器来存储,插件与外界交互都通过关系管理器来实现。关系是在配置文件中指定,分析配置文件的时候就会将配置中指定的关系注册到关系管理器中。
在运行期,插件动态从关系管理器中取得和自己关联的接口。
懒加载
为了节省用户资源,需要实现插件的按需加载,也叫懒加载,只有用到的插件才会从文件中装载到内存中运行。
实现懒加载需要处理导航器和插件的布局。很多地方需要绑定插件的信息,但这时插件对象还不存在。使用代理插件可以解决这个问题。
所有与插件的通信都通过代理插件对象来中转。代理对象由主框架创建,记录插件的基本信息。在系统装载期,绑定到系统中的接口都是代理对象,当外界需要与插件交互,例如显示、运行某个方法的时候,由代理来自动装载真实的插件,然后将调用委派给插件来响应。这样可以让懒加载过程对于系统装载,插件运行是透明的。
架构对比
微内核 VS 巨内核
Eclipse中的运行框架非常小,系统中几乎所有的都是插件,采用的是微内核+插件的形式。在后面介绍的插件架构中系统运行框架比较复杂,它包括了界面布局策略、导航、插件代理等职责,可以说是巨内核+插件的形式。
微内核与巨内核之争已经有很长历史了。在操作系统的概念中尤为突出。网上对于微内核与巨内核的讨论同样适用于插件系统。
仅从上面介绍的两种插件系统来看,微内核的好处在于系统的可扩展性强,如果你愿意,甚至可以将Eclipse整个开发环境都替换掉;巨内核的好处在于插件非常简单,只需要将业务部分用统一的接口公布出来就可以,在开发具体模块的时候可以不用考虑开发的是否是插件。
界面呈现
微内核中的界面呈现完全由父插件来决定,留了什么样的扩展点就可以在界面上以什么样的形式发布功能。
巨内核中的界面呈现由系统运行框架决定,框架支持了几种显示的模式。配置文件可以在现有的模式之上随意组合形成复杂的界面。在这个过程中插件并不关心自己被放在什么地方,或者以什么形式呈现。
插件关系
微内核中的插件关系由插件自身来维持,插件实现的扩展决定了它和父插件之间的交互关系,新增的扩展点决定了它和将来在它基础上扩展的插件交互的模式。
巨内核中的插件关系由系统框架(关系管理器)统一管理,插件本身不需要维护交互信息,只有在需要的时候才会从关系管理器取得。
懒加载
两种架构都可以支持插件的懒加载。基本的思路是一致的。但微内核中的插件装载由父插件来完成,而巨内核中的装载则直接由系统框架提供的统一代理类来完成。