插件系统构建

 

插件系统概述

普通的系统,在编译发布以后,系统就不容许进行更改或扩充了,若是要进行某个功能的扩充,则必需要修改代码从新编译发布。使用插件能够很好地解决这个问题。html

插件概念

首先由开发人员编写系统框架,并预先定义好系统的扩展借口。插件由其余开发人员根据系统预约的接口编写的扩展功能,实际上就是系统的扩展功能模块。插件都是以一个独立文件的形式出现。前端

对于系统来讲并不知道插件的具体功能,仅仅是为插件留下预约的接口,系统启动的时候根据插件的配置寻找插件,根据预约的接口把插件挂接到系统中。设计模式

优点

1、系统的扩展性大大地增强了。若是咱们在系统发布后须要对系统进行扩充,就没必要从新编译,只须要增长或修改插件就能够了。架构

2、有利于模块化的开发方式。咱们能够开发强大的插件管理系统,在这样的一个插件系统下,咱们能够不修改基本系统,仅仅使用插件就能构造出各类各样不一样的系统。框架

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就是一个插件,他提供了新的扩展点供其余插件来扩展。布局

clip_image001

扩展点

能够看到Eclipse的插件结构是由父插件管理子插件,插件之间由扩展点链接,最终造成树形的结构。操作系统

界面呈现

界面呈现由提供扩展点的父插件来决定,好比说父插件在菜单上留了扩展点,那么子插件就能够出如今菜单项上。界面呈现的类型是由提供扩展的插件决定。插件

插件交互

插件之间的交互经过扩展点实现。父插件调用子插件实现的扩展点来触发子插件的动做。

依赖关系

配置文件中指定插件运行须要依赖的插件,在装载过程当中会按照依赖的关系顺序来装载。

扩展点造成的系统结构

Eclipse中的插件用扩展点的机制链接起来,造成以下图所示的系统结构。插件必须实现扩展点,以此插入到系统中,新增扩展点并非必须的,但只有新增了扩展点的插件才能够被别人扩展。

clip_image002

懒加载

只有在调用执行动做的时候才会将真实的动做对象建立起来。因为在配置文件中已经具有真实动做的一切信息,因此在不装载插件时,一样能够在父插件的界面上将扩展的功能显示出来。

另外一个插件系统

插件结构

插件分为“插件外壳”和“业务”两部分。
其中业务部分与插件没有任何关系,按照通常的应用程序开发便可。最终提供给插件外壳一个主要的界面和公布出来的方法。
插件外壳提供接口供外界调用。系统和其它插件彻底经过插件外壳和插件进行交互。

clip_image003

界面呈现

将每一个插件的界面按照必定形式组织起来生成整个系统。界面组织的规则在配置文件中指定。系统提供可配置的方案。

布局(Layout)

插件按照必定的布局放到整个系统的界面中。在目前的系统内提供了三种布局。

页面布局

将插件按照页面的形式重叠在一块儿,插件激活时将本身所属的页面翻转到最前端。

clip_image004

模块布局

将插件按照模块划分放到同一个界面显示。模块之间用分割条链接。

clip_image005

页签布局

将插件按照页签的形式放到一块儿。

clip_image006

装饰(Decorator)

布局指定了插件出现的位置与形式。装饰能够指定插件出现的方式。

可关闭装饰

指定插件出现的部分是否能够关闭。在普通的模式下,插件能够按照上面的几种版型出现,但这时的插件界面是不可关闭的。若是须要增长关闭功能,能够给插件指定一个装饰器。

下面举一个在模块布局中的模块2上应用“可关闭装饰”的例子。

clip_image007

布局、装饰的组合

上面列举了现有的布局与装饰,复杂界面一样能够有布局与装饰的组合来完成。这里的图式代表将三种布局与装饰组合的一种状况。

clip_image008

经过配置文件指定出不一样的组合状况就能够完成更多的界面布局了。在更改整个系统界面布局的时候只须要修改配置文件,程序并不须要从新发布。

导航

经过配置文件装配好的插件系统,界面多是很是复杂的。这种状况下要让用户找到想要的功能须要用导航器来呈现系统提供的所用功能。
系统提供的功能就是插件提供的功能的集合,插件提供的功能经过插件外壳公布出来。公布的方式依照语言的特性来定:C#、Java中能够利用反射机制运行公布出来的方法,Delphi中用RTTI也能够一样运行配置文件中指定的方法。

常见的导航器均可以抽象成树形结构。每个导航单元映射到一个用户须要的功能,每个功能对应到具体的插件的某一个方法。将功能抽象成一个Action对象,对象须要知道它导向的插件和方法名。

clip_image009

能够在上面抽象模型的基础上实现任意形式的导航器。能够是菜单项,能够是TreeView,也能够是自定义的控件。

交互关系

系统须要知道插件的操做,插件与插件之间一样也会有交互。

将全部的交互关系用一个关系管理器来存储,插件与外界交互都经过关系管理器来实现。关系是在配置文件中指定,分析配置文件的时候就会将配置中指定的关系注册到关系管理器中。
在运行期,插件动态从关系管理器中取得和本身关联的接口。

clip_image010

懒加载

为了节省用户资源,须要实现插件的按需加载,也叫懒加载,只有用到的插件才会从文件中装载到内存中运行。

实现懒加载须要处理导航器和插件的布局。不少地方须要绑定插件的信息,但这时插件对象还不存在。使用代理插件能够解决这个问题。

clip_image011

全部与插件的通讯都经过代理插件对象来中转。代理对象由主框架建立,记录插件的基本信息。在系统装载期,绑定到系统中的接口都是代理对象,当外界须要与插件交互,例如显示、运行某个方法的时候,由代理来自动装载真实的插件,而后将调用委派给插件来响应。这样可让懒加载过程对于系统装载,插件运行是透明的。

架构对比

微内核 VS 巨内核

Eclipse中的运行框架很是小,系统中几乎全部的都是插件,采用的是微内核+插件的形式。在后面介绍的插件架构中系统运行框架比较复杂,它包括了界面布局策略、导航、插件代理等职责,能够说是巨内核+插件的形式。

微内核与巨内核之争已经有很长历史了。在操做系统的概念中尤其突出。网上对于微内核与巨内核的讨论一样适用于插件系统。

仅从上面介绍的两种插件系统来看,微内核的好处在于系统的可扩展性强,若是你愿意,甚至能够将Eclipse整个开发环境都替换掉;巨内核的好处在于插件很是简单,只须要将业务部分用统一的接口公布出来就能够,在开发具体模块的时候能够不用考虑开发的是不是插件。

界面呈现

微内核中的界面呈现彻底由父插件来决定,留了什么样的扩展点就能够在界面上以什么样的形式发布功能。

巨内核中的界面呈现由系统运行框架决定,框架支持了几种显示的模式。配置文件能够在现有的模式之上随意组合造成复杂的界面。在这个过程当中插件并不关心本身被放在什么地方,或者以什么形式呈现。

插件关系

微内核中的插件关系由插件自身来维持,插件实现的扩展决定了它和父插件之间的交互关系,新增的扩展点决定了它和未来在它基础上扩展的插件交互的模式。

巨内核中的插件关系由系统框架(关系管理器)统一管理,插件自己不须要维护交互信息,只有在须要的时候才会从关系管理器取得。

懒加载

两种架构均可以支持插件的懒加载。基本的思路是一致的。但微内核中的插件装载由父插件来完成,而巨内核中的装载则直接由系统框架提供的统一代理类来完成。

 

 

 

 

 

 

========================================================================================================

========================================================================================================

 

一切都是为了更加简单。

从函数到函数库,而后到类,而后到插件,都是由于咱们的软件系统日益复杂,人脑毕竟有限,不能同时处理那么多的信息量,因此采用分而治之的方法来管理。
今年已经研究了一年的插件系统,从最开始的懵懵懂懂到如今能有些经验和你们分享,这个过程自己就是颇有意思的。

最开始系统中有了十几个插件,通过几个月的慢慢发展,到了大几十个,甚至上百个,这个数量就有些使人头晕了。不过更加麻烦的还不是这近百个插件组装而成的系统,而是某一个插件系统须要调用另外的一个或多个插件系统。这样的话,插件的数量就在100的基数上开始翻倍。

如何作插件系统中的整合成了一个紧急的课题。

1、插件系统基本结构

前面写过一篇文章,说到了插件系统中的微内核与巨内核之分。不过无论是哪种,任何一个系统都须要有一个启动点,只不过对于插件系统中的启动步骤来讲,它是一个通用,而且和具体业务无关的独立模块。

能够按照下面的图示来简单理解插件系统:

clip_image012

图中的Launcher是插件系统的启动模块,EntryPoint是系统的入口点,做为一个接口给Launcher调用。启动模块经过EntryPoint将系统运行起来。系统中的插件相互协做知足用户的须要。

2、开始集成

上面的图将一个插件系统的基本原素描绘出来了。在具体的项目中,这样的一个插件系统中插件的数量可能多达上百个。当两个项目组都在开发各自的产品,项目组A须要将项目组B开发的系统集成到本身的系统中时,就要开始考虑集成的问题了。

系统中的插件之间存在父子关系,任何一个插件均可以做为另一个插件的子插件存在。

若是将系统B做为系统A中某一个插件的子插件是否是就能够解决集成问题了呢?——不错,一个简单但实用的解决方法。

能够将插件系统考虑成一个函数库,函数库中的几百个函数相互协做完成一系列复杂的功能。如今咱们须要在本身写的函数中包含上面函数库中的全部功能怎么办,简单的作法是将函数库中的某个入口函数做为子函数调用就能够了。

下面介绍的集成方案基本上就是这个思路。

3、插件系统集成解决方案

clip_image013

3.1 EntryPoint与Endpoint

EntryPoint是插件系统的启动模块调用系统功能的接口,这个接口是很是简单的,不少时候仅有一个Run方法,直接对应到用户的双击打开程序的操做。

在系统A中要调用系统B时,显然一个简单的Run方法不能知足要求,这里另外提出一个系统的入口点(端点)Endpoint。

二者的区别在于,EntryPoint对应到Launcher的启动过程,参数简单;Endpiont对应到其余系统的交互过程,参数复杂,须要经过Endpoint传递其余系统须要的信息。

3.2 BUS

有了每一个系统的端点,还须要将这些端点组合起来,保证插件系统之间的相互通讯。相似于电脑中的总线概念。一旦每一个系统的Endpoint挂接到了总线上,插件系统就能够经过总线查找到本身须要交互的其余插件系统了。

这里的总线用关系管理器来实现。由于Endpoint在插件系统中也是做为一个插件存在,这个插件的职责就是和外界交互。关系管理器能够处理任何插件之间的交互,尽管插件并不在同一个系统中。

3.3 Linker

在系统A中呈现系统B的功能有多种表现形式,好比说在系统A的某个地方放上一个Button,点击后系统B出现;或者在系统A中放上一个页签,和通常功能并列将系统B呈如今系统A中。无论怎样呈现,能够将系统B看做系统A的一个插件。这个插件就是图中的Linker。

Linker是系统B的一个代理插件,它自己并无实现业务,只是将与系统B的交互以插件的形式呈如今系统A中。Linker经过总线找到对应的插件系统并将它启动,同时负责与它的交互。

4、适配器模式

Endpoint用的是Adapter的思想,将自身系统的功能以规定好的交互方式发布到总线上,这样其余插件系统才能与之进行交互。这种方法在系统的集成中用得很是多,已经从设计模式上升到了架构模式的层次。

有了这种适配器的方式,不只仅是插件系统能够集成,甚至非插件系统一样也能够集成到插件系统中来。所做的就是须要给非插件系统提供一个Adapter插件。对于其余插件系统来讲,这个非插件系统在BUS的表现也和插件系统没有差异了。

clip_image014

5、说时容易作时难

上面提出了一种插件系统的集成方案,目前正在逐步的尝试,过程当中还遇到了一些细节上的问题,从此等我慢慢整理出来再和你们分享。

如今只是作了插件系统与插件系统之间的集成,虽然从理论上说,插件系统与非插件系统的集成也一样可行,不过目前尚未实践,不敢妄下定论。等有了机会再好好研究一下这方面的内容。若是哪位朋友有一些好的经验愿意分享,在下洗耳恭听:)

 

 

 

 

========================================================================================================

========================================================================================================

插件能够封装必定的业务,一样控件也具备封装性。

能够说控件的出现大大简化了咱们开发的工做量。做为一个插件系统来讲,实现一个通用的插件能在更大粒度上进行复用。插件是比控件更加高层的一种模块封装方式。

插件和控件有相同的地方:封装和复用。本文分析了它们的异同,而且提出另一个比较有趣的概念——伪插件。请你们继续往下读一读。

1、插件和控件的比较

发布

控件编译到系统中,和系统做为一个总体发布。

插件是在系统的运行过程当中动态关联到系统上,能够和系统的其余部分保持物理上的隔离。

配置能力

控件在系统中的呈现方式在编译时已经肯定,经过代码描述控件的表现形式,呈现位置等。

插件的呈现方式在运行的时候根据外部的配置文件指定。

功用

控件做为公用的组件使用,在咱们编写业务模块时,控件做为基本资源被咱们使用。

插件做为一个独立的业务模块存在,直接面向用户。

开发调试

控件的调试简单,但插件的调试却比较麻烦。正是由于为了灵活性而制造的隔离措施致使了调试上的困难。一般一个插件做为一个工程开发。

2、插件与控件的关系

插件是业务模块,就像上面所说的,在咱们编写业务模块时控件做为基本资源被使用。因此插件与控件的关系以下图左所示,普通的业务模块以下图右所示。

clip_image015

能够看到,插件是知足必定接口协议的业务模块。

3、混乱的界限

做为控件使用的插件

若是一个插件中只有一个控件,而且没有其余的业务逻辑。这种状况下它是插件仍是控件?

clip_image016

就像上面所说的,插件是带有必定业务的模块,而且是直接面向用户做为一个系统功能来体现的。插件仅仅是封装了一个控件,并无带有其余的业务。像这种模块是做为其余插件的子插件使用。以下图所示。

clip_image017

这和咱们上面看到的插件内部直接包含控件就不同了。控件做为子插件的形式被其余插件使用。

插件的配置文件中会将自身的属性做为配置,如标题、图标、和其余一切能够做为配置的元素。但子插件没有详细的配置文件,它的属性直接经过插件的接口暴露给父插件。

这类的子插件是介于插件与控件之间的“伪插件”,由于它并不能独立地在系统中运行,而且一般状况下不带有业务逻辑,不能直接给用户带来价值。

发布后可更换控件

伪插件彷佛没有什么好处,谁会平白无故地在控件之上再封装一层做为插件来使用?

能够想象一下,在系统发布后,咱们须要改变某些插件中使用的控件。固然,能够将那些插件所有从新编译后发布。但若是使用这种“伪插件”的思路,咱们能够开发一个知足一样接口的另一个伪插件,并在内部使用不一样的控件实现。这样就能够在不发布其余插件的状况下,灵活地修改咱们使用的控件了。

额外开销

若是全部的控件都像上面的来实现,那简直是一场恶梦,而且也没有这个必要。由于这样作的成本比较大。

至于实际中是直接用控件,仍是用伪插件的技术,那就要看咱们的决策了。