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

插件体系结构软件开发方法研究

2018年03月15日 ⁄ 综合 ⁄ 共 54979字 ⁄ 字号 评论关闭

 

 

插件体系结构软件开发方法研究

 

计算机软件与理论专业

 

研究生
一觉亮天

 

随着计算机技术的发展,软件体系结构和开发方法也在发生着重大变化。为了高效率地进行软件开发,并且开发出高质量的产品,人们一直在寻求更好的软件体系结构和开发方法。插件式软件体系结构和开发方法正得到越来越多的应用,当前有很多成功的软件产品采用此种体系结构来开发。

插件式体系结构是一种非常灵活的组件式结构,它把程序的功能分散在插件中来完成,插件是独立于系统可独立开发的程序模块,它能够动态地插入到系统中,并且插件可以被自由地插入、删除和替换。因此它有着相当突出的优点:能够提高软件开发的并行性和开发效率,降低设计开发难度,缩短开发周期,增强应用程序的可运行性、可测试性和可维护性。因此对如何开发基于插件式体系结构的软件进行研究,具有现实的实用意义。

本文首先分析了插件式体系结构软件的结构和工作原理,详细地对插件系统的设计思想,开发中的原则、建议、技术方法以及可行性进行了总体的细致深入的分析。SharpDevelop是采用微软.NET技术基于插件树体系结构开发的成功的应用和影响广泛的开源的集成开发环境。接着本文对SharpDevelop进行了仔细研究,分析了它的插件管理程序的实现细节和如何用插件组织应用程序。最后本文利用之前的研究成果,利用SharpDevelop提供的接口,开发了一个简单的插件,并成功地装配到SharpDevelop中。

插件体系结构软件开发方法已经具有了成功的应用,对它进行研究,使这种方法应用更加广泛,对提高软件开发效率和软件质量有很大帮助。

关键词插件
软件体系结构
设计模式


 

 

Research on Plug-in Architecture

 

MajorComputer Software and Theory

 

PostgraduateYiJiaoLiangTian

 

With the development of computertechnology, great changes have happened to software architecture and developingways. Developers have been seeking better architecture and ways to improve developingefficiency and software
quality. Currently, a kind of architecture, which baseson plug-ins, is getting more and more adopted and many successive softwareproducts have been developed according to it.

Software basing on plug-ins separatesits functions into different plug-ins, so it is convenient to design anddevelop. Plug-ins can independently be programmed and plugged into applicationsdynamically. The software can
add, delete and replace plug-ins easily, anddevelopers can benefit a lot from this character. It not only makes developingdifferent parts of software synchronously possible but also improves developingefficiency. Besides, it also makes the process of designing
and developingeasier so as to reduce the costs of time. Meanwhile, it makes the program easyto execute, test and maintain. For all reasons above, researches onplug-in-oriented architecture is meaningful.

In this article, firstly, the structureand theory of plug-in-oriented architecture are studied, and detailed analysesfocusing on designing thoughts, developing principles and feasibility areprovided. Then studies on the
details of SharpDevelop are undertaken.SharpDevelop is an open-sourced IDE programmed using Microsoft.NET. Thearchitecture, which is called add-in tree, is successfully applied inSharpDevelop. Nowadays, SharpDevelop is widely used and studied by more andmore
people. At last, through making use of the former studies on SharpDevelop,a plug-in of SharpDevelop is developed.

Architecture basing on plug-in has beenapplied successfully, and studying on it so as to make it adopted widely willabsolutely improve developing efficiency and quality.

Key wordsplug-in, software architecture, design patterns


 

 

1.绪论...
1

1.1引言...
1

1.2软件体系结构的现状...
1

1.3软件开发方法的历史与现状...
2

1.3.1Parnas方法...
2

1.3.2Yourdon方法...
3

1.3.3面向数据结构的软件开发方法...
3

1.3.4问题分析法...
4

1.3.5面向对象的软件开发方法...
4

1.3.6可视化开发方法...
6

1.3.7集成计算机辅助软件工程...
8

1.3.8软件重用和组件连接...
9

1.4研究的意义...
10

2.插件体系结构开发方法...
12

2.1插件的概念...
12

2.2插件体系结构开发方法的优点...
12

2.3插件与组件...
13

2.4插件程序的应用领域...
14

2.5插件程序的发展方向...
15

3.基于.NET的插件体系结构总述...
17

3.1插件结构的分析...
17

3.1.1插件结构...
17

3.1.2研究插件结构的目的...
18

3.2基于.NET的插件体系结构技术基础...
18

3.2.1微软的.NET技术...
18

3.2.2XML技术...
24

3.2.3面向对象的程序设计方法...
26

3.2.4设计模式...
26

3.3插件体系结构的接口...
28

3.3.1插件体系结构接口概述...
28

3.3.2插件接口的特点...
29

3.4接口的智能化识别...
31

3.4.1接口规划与数据结构...
31

3.4.2智能化识别的过程...
31

3.4.3识别过程中应注意的问题及解决的办法...
32

3.5插件的自动化调用...
32

3.5.1插件管理程序对插件的调用...
32

3.5.2调用过程中的参数问题...
33

3.6插件管理程序与插件间的通信机制...
34

3.7插件系统接口的管理和控制...
34

3.7.1什么是接口的管理控制...
34

3.7.2为什么需要接口的管理控制...
35

3.7.3接口控制管理的内容...
36

3.7.4接口的升级...
37

4.基于.NET的插件体系结构的实现...
40

4.1插件树体系结构的实例...
40

4.2插件树...
41

4.2.1插件树的概念...
41

4.2.2插件树的优点...
42

4.2.3插件树的上层结构...
42

4.3代码子...
44

4.4条件...
45

4.5插件管理...
47

4.6属性管理...
49

4.7用插件创建应用程序...
51

4.7.1使用代码子...
51

4.7.2通过条件接合插件...
53

5.通过插件扩充功能...
56

5.1插件简介...
56

5.2插件的配置...
58

5.3插件的实现代码...
59

6.结论...
65

参考文献...
66

作者发表论文...
67

独创性声明...
68

致谢...
69

 


1.绪论

1.1引言

自从计算机诞生以来,如何高效地开发程序就一直是软件开发人员热衷于研究的领域。但计算机诞生之初,计算机硬件较为昂贵,应用的领域还并不广泛,计算机软件也还仅局限于少量特定目的使用上,因此在当时,软件技术的研究也大都考虑如何提高程序运行的速度,减少对硬件环境的需求。二十世纪八十年代后,计算机技术经历了迅猛的发展,硬件价格日趋下降,并且越来越小型化,逐渐进入到企业并且普及到家庭,应用范围也涉及到整个社会日常生活的几乎方方面面,从航天飞机到家庭娱乐,从气象预报到股票交易,软件的功能也越来越复杂,界面越来越友好,操作也越来越简便。同时硬件的发展使得软件也几乎摆脱了它的束缚,操作系统提供的强大功能使得程序员能够更多地把精力放在软件开发本身上。在硬件以摩尔定律发展的时候,软件的应用领域迅速扩大,新的需求层出不穷,软件的升级换代也越来越快。传统的软件开发方法在现代的多样需求下显得力不从心,这就需要我们寻找一种新的更好的开发方法。

传统的软件开发方法基于传统的体系结构,最初的软件通常都是单一模块的执行程序,在早期的开发中,大多数任务都是功能较为简单的程序,因此容易自顶向下逐层细分划分为较小的模块进行分别开发。在有新的需求出现时,即使全部重新开发,也相对容易,这样软件的升级和换代就并非很困难的事情。但是传统的计算机开发方法在现代计算机的发展和应用水平下难以适应。首先软件的复杂程度超过以前,完整开发出一个软件需要大量的人力和物力,并且需要很长的软件开发周期,而且软件升级和更新换代的要求往往很快,因此需要新的软件体系结构和新的软件开发方法来适应新的需求。

 

1.2软件体系结构的现状

在二十世纪四十年代计算机问世之后,硬件的价格一直居高不下,计算机资源包括处理器的能力和存储设备的能力都很有限,并且很紧张。程序员在编写程序的过程中,必须考虑节省资源,以便能够在有限的情况下运行,因此几乎所有程序都是采用单一模块的可执行程序结构,甚至很多程序都采用了汇编语言进行编程,当时对体系结构的研究很大程度上是为了使运行代码更紧凑,运行速度更快,如何提高软件的开发效率只是其中很少考虑的一部分。

随着科学技术的发展,除了特殊领域的特殊需求,减少程序运行的开销,已经不是开发程序的首要目标了。而程序员可以更多地考虑如何优化程序结构以便方便地支持更多需求。这些需求刺激了新技术的出现,新技术的出现满足了这些需求,并且促进软件体系结构和开发方法的发展。如动态链接技术,面向对象技术,微软的COMSun公司的EJB,还有微软的.NET等。这些新技术的出现,对于提高软件开发效率具有重大意义。我们可以不必局限于一次开发出完整的软件,也不必在更新换代时重新修改或编写所有的程序。以这些新的技术为基础,我们就能够克服传统方法的限制,构建出新的软件体系结构发展新的软件开发方法提高软件开发效率。

当前,传统的软件体系结构仍然在许多应用领域占据着主要地位,但随着技术的进步和整个软件产业的规模化生产,一些新的软件体系结构也在不断地形成和发展中。现在主要有以下一些结构:数据流系统(DATAFLOWSYSTEM),调用返回系统(CALL-AND-RETURN
SYSTEM),独立部件(INDEPENDENT COMPONENT),虚拟机(VIRTUAL MACHINES),面向数据中心系统(DATA-CENTERED SYSTEM),等等。

 

1.3软件开发方法的历史与现状

60年代中期开始爆发了众所周知的软件危机。为了克服这一危机,在19681969年连续召开的两次著名的NATO会议上提出了软件工程这一术语,并在以后不断发展、完善。与此同时,软件研究人员也在不断探索新的软件开发方法。至今已形成八类软件开发方法。

 

1.3.1Parnas方法

最早的软件开发方法是由D.Parnas1972年提出的。由于当时软件在可维护性和可靠性方面存在着严重问题,因此Parnas提出的方法是针对这两个问题的。首先,Parnas提出了信息隐蔽原则:在概要设计时列出将来可能发生变化的因素,并在模块划分时将这些因素放到个别模块的内部。这样,在将来由于这些因素变化而需修改软件时,只需修改这些个别的模块,其它模块不受影响。信息隐蔽技术不仅提高了软件的可维护性,而且也避免了错误的蔓延,改善了软件的可靠性。现在信息隐蔽原则已成为软件工程学中的一条重要原则。

Parnas提出的第二条原则是在软件设计时应对可能发生的种种意外故障采取措施。软件是很脆弱的,很可能因为一个微小的错误而引发严重的事故,所以必须加强防范。如在分配使用设备前,应该取设备状态字,检查设备是否正常。此外,模块之间也要加强检查,防止错误蔓延。

Parnas对软件开发提出了深刻的见解。遗憾的是,他没有给出明确的工作流程。所以这一方法不能独立使用,只能作为其它方法的补充。

 

1.3.2Yourdon方法

1978年,E.YourdonL.L.Constantine提出了结构化方法,也可称为面向功能的软件开发方法或面向数据流的软件开发方法。1979Tom
DeMarco对此方法作了进一步的完善。

Yourdon方法是80年代使用最广泛的软件开发方法。它首先用结构化分析(SA)对软件进行需求分析,然后用结构化设计(SD)方法进行总体设计,最后是结构化编程(SP)。这一方法不仅开发步骤明确,SASDSP相辅相成,一气呵成,而且给出了两类典型的软件结构——变换型和事务型,便于参照,使软件开发的成功率大大提高,从而深受软件开发人员的青睐。

 

1.3.3面向数据结构的软件开发方法

1975年,M.A.Jackson提出了一类至今仍广泛使用的软件开发方法,被称为Jackson方法。这一方法从目标系统的输入、输出数据结构入手,导出程序框架结构,再补充其它细节,就可得到完整的程序结构图。这一方法对输入、输出数据结构明确的中小型系统特别有效,如商业应用中的文件表格处理。该方法也可与其它方法结合,用于模块的详细设计。

Jackson方法有时也称为面向数据结构的软件设计方法。

1974年,J.D.Warnier提出的软件开发方法与Jackson方法类似的方法,被称为Warnier方法。差别有三点:一是它们使用的图形工具不同,分别使用Warnier图和Jackson;另一个差别是使用的伪码不同;最主要的差别是在构造程序框架时,Warnier方法仅考虑输入数据结构,而Jackson方法不仅考虑输入数据结构,而且还考虑输出数据结构。

 

1.3.4问题分析法

问题分析法(PAMProblem Analysis Method)是80年代末由日立公司提出的一种软件开发方法。

PAM方法希望能兼顾Yourdon方法、Jackson方法和自底向上的软件开发方法的优点,而避免它们的缺陷。它的基本思想是:考虑到输入、输出数据结构,指导系统的分解,在系统分析指导下逐步综合。这一方法的具体步骤是:从输入、输出数据结构导出基本处理框;分析这些处理框之间的先后关系;按先后关系逐步综合处理框,直到画出整个系统的PAD图。从上述步骤中可以看出,这一方法本质上是综合的自底向上的方法,但在逐步综合之前已进行了有目的的分解,这个目的就是充分考虑系统的输入、输出数据结构。

PAM方法的另一个优点是使用PAD图。这是一种二维树形结构图,是到目前为止最好的详细设计表示方法之一,远远优于N-S图和PDL语言。

这一方法在日本较为流行,软件开发的成功率也很高。由于在输入、输出数据结构与整个系统之间同样存在着鸿沟,这一方法仍只适用于中小型问题。

 

1.3.5面向对象的软件开发方法

面向对象技术是软件技术的一次革命,在软件开发史上具有里程碑的意义。

随着OOP(面向对象编程)向OOD(面向对象设计)和OOA(面向对象分析)的发展,最终形成面向对象的软件开发方法OMTObject
Modelling Technique)。这是一种自底向上和自顶向下相结合的方法,而且它以对象建模为基础,从而不仅考虑了输入、输出数据结构,实际上也包含了所有对象的数据结构。所以OMT彻底实现了PAM没有完全实现的目标。不仅如此,OO技术在需求分析、可维护性和可靠性这三个软件开发的关键环节和质量指标上有了实质性的突破
,彻底地解决了在这些方面存在的严重问题,从而宣告了软件危机末日的来临。

面向对象的开发方法首先采用自底向上的归纳方法。OMT的第一步是从问题的陈述入手,构造系统模型。从真实系统导出类的体系,即对象模型包括类的属性,与子类、父类的继承关系,以及类之间的关联。类是具有相似属性和行为的一组具体实例(客观对象)的抽象,父类是若干子类的归纳。因此这是一种自底向上的归纳过程。在自底向上的归纳过程中,为使子类能更合理地继承父类的属性和行为,可能需要自顶向下的修改,从而使整个类体系更加合理。由于这种类体系的构造是从具体到抽象,再从抽象到具体,符合人类的思维规律,因此能更快、更方便地完成任务。这与自顶向下的Yourdon方法构成鲜明的对照。在Yourdon方法中构造系统模型是最困难的一步,因为自顶向下的""是一个空中楼阁,缺乏坚实的基础,而且功能分解有相当大的任意性,因此需要开发人员有丰富的软件开发经验。而在OMT中这一工作可由一般开发人员较快地完成。在对象模型建立后,很容易在这一基础上再导出动态模型和功能模型。这三个模型一起构成要求解的系统模型。

面向对象的开发方法然后采用自顶向下的分解方法。系统模型建立后的工作就是分解。与Yourdon方法按功能分解不同,在OMT中通常按服务
Service)来分解。服务是具有共同目标的相关功能的集合,如I/O处理、图形处理等。这一步的分解通常很明确,而这些子系统的进一步分解因有较具体的系统模型为依据,也相对容易。所以OMT也具有自顶向下方法的优点,即能有效地控制模块的复杂性,同时避免了Yourdon方法中功能分解的困难和不确定性。

OMT的基础是对象模型。每个对象类由数据结构(属性)和操作(行为)组成,有关的所有数据结构(包括输入、输出数据结构)都成了软件开发的依据。因此Jackson方法和PAM中输入、输出数据结构与整个系统之间的鸿沟在OMT中不再存在。OMT不仅具有Jackson方法和PAM的优点,而且可以应用于大型系统。更重要的是,在Jackson方法和PAM方法中,当它们的出发点——输入、输出数据结构(即系统的边界)发生变化时,整个软件必须推倒重来。但在OMT中系统边界的改变只是增加或减少一些对象而已,整个系统改动极小。

面向对象的开发方法使需求分析进行得彻底。需求分析不彻底是软件失败的主要原因之一。即使在目前,这一危险依然存在。传统的软件开发方法不允许在开发过程中用户的需求发生变化,从而导致种种问题。正是由于这一原因,人们提出了原型化方法,推出探索原型、实验原型和进化原型,积极鼓励用户改进需求。在每次改进需求后又形成新的进化原型供用户试用,直到用户基本满意,大大提高了软件的成功率。但是它要求软件开发人员能迅速生成这些原型,这就要求有自动生成代码的工具的支持。

OMT彻底解决了这一问题。因为需求分析过程已与系统模型的形成过程一致,开发人员与用户的讨论是从用户熟悉的具体实例(实体)开始的。开发人员必须搞清现实系统才能导出系统模型,这就使用户与开发人员之间有了共同的语言,避免了传统需求分析中可能产生的种种问题。

面向对象的开发方法使系统可维护性大大改善。在OMT之前的软件开发方法都是基于功能分解的。尽管软件工程学在可维护方面作出了极大的努力,使软件的可维护性有较大的改进。但从本质上讲,基于功能分解的软件是不易维护的。因为功能一旦有变化都会使开发的软件系统产生较大的变化,甚至推倒重来。更严重的是,在这种软件系统中,修改是困难的。由于种种原因,即使是微小的修改也可能引入新的错误。所以传统开发方法很可能会引起软件成本增长失控、软件质量得不到保证等一系列严重问题。正是OMT才使软件的可维护性有了质的改善。

OMT的基础是目标系统的对象模型,而不是功能的分解。功能是对象的使用,它依赖于应用的细节,并在开发过程中不断变化。由于对象是客观存在的,因此当需求变化时对象的性质要比对象的使用更为稳定,从而使建立在对象结构上的软件系统也更为稳定。

更重要的是OMT彻底解决了软件的可维护性。在OO语言中,子类不仅可以继承父类的属性和行为,而且也可以重载父类的某个行为(虚函数)。利用这一特点,我们可以方便地进行功能修改:引入某类的一个子类,对要修改的一些行为(即虚函数或虚方法)进行重载,也就是对它们重新定义。由于不再在原来的程序模块中引入修改,所以彻底解决了软件的可修改性,从而也彻底解决了软件的可维护性。OO技术还提高了软件的可靠性和健壮性。

 

1.3.6可视化开发方法

可视化开发是90年代软件界最大的两个热点之一。随着图形用户界面的兴起,用户界面在软件系统中所占的比例也越来越大,有的甚至高达6070%。产生这一问题的原因是图形界面元素的生成很不方便。为此Windows提供了应用程序设计接口APIApplicationProgramm
ing Interface),它包含了600多个函数,极大地方便了图形用户界面的开发。但是在这批函数中,大量的函数参数和使用数量更多的有关常量,使基于WindowsAPI的开发变得相当困难。为此Borland C++推出了Object
Windows编程。它将API的各部分用对象类进行封装,提供了大量预定义的类,并为这些定义了许多成员函数。利用子类对父类的继承性,以及实例对类的函数的引用,应用程序的开发可以省却大量类的定义,省却大量成员函数的定义或只需作少量修改以定义子类。ObjectWindows还提供了许多标准的缺省处理,大大减少了应用程序开发的工作量。但要掌握它们,对非专业人员来说仍是一个沉重的负担。为此人们利用WindowsAPIBorland
C++Object Windows开发了一批可视开发工具。

可视化开发就是在可视开发工具提供的图形用户界面上,通过操作界面元素,诸如菜单、按钮、对话框、编辑框、单选框、复选框、列表框和滚动条等,由可视开发工具自动生成应用软件。

这类应用软件的工作方式是事件驱动。对每一事件,由系统产生相应的消息,再传递给相应的消息响应函数。这些消息响应函数是由可视开发工具在生成软件时自动装入的。

可视开发工具应提供两大类服务。一类是生成图形用户界面及相关的消息响应函数。通常的方法是先生成基本窗口,并在它的外面以图标形式列出所有其它的界面元素,让开发人员挑选后放入窗口指定位置。在逐一安排界面元素的同时,还可以用鼠标拖动,以使窗口的布局更趋合理。

另一类服务是为各种具体的子应用的各个常规执行步骤提供规范窗口,它包括对话框、菜单、列表框、组合框、按钮和编辑框等,以供用户挑选。开发工具还应为所有的选择(事件)提供消息响应函数。

由于要生成与各种应用相关的消息响应函数,因此,可视化开发只能用于相当成熟的应用领域,如目前流行的可视化开发工具基本上用于关系数据库的开发。对一般的应用,目前的可视化开发工具只能提供用户界面的可视化开发。至于消息响应函数,则仍需用通常的高级语言编写。

从原理上讲,与图形有关的所有应用都可采用可视化开发方式,如活塞表面设计中的热应力计算。用户只需在界面上用鼠标修改活塞表面的曲线,应用软件就自动进行有限元划分、温度场计算、热应力计算,并将热应力的等值曲线图显示在屏幕上。最后几次生成的结果还可并列显示在各窗口上,供用户比较,其中的一个主窗口还可让用户进一步修改活塞表面曲线。

许多工程科学计算都与图形有关,从而都可以开发相应的可视化计算的应用软件。

可视化开发是软件开发方式上的一场革命,它使软件开发从专业人员的手中解放出来,对缓解80年代中后期爆发的应用软件危机有重大作用。

 

1.3.7集成计算机辅助软件工程

提高人类的劳动生产率,提高生产的自动化程度,一直是人类坚持不懈的追求目标。软件开发也不例外。早在1982年美国国防部就提出了STARS工程,希望建立一个"用以支持需求定义、程序生成以及软件维护等软件生存期全部活动的,并把它们集成在一起的整个体系"。但早期的软件开发环境工具较少,且不配套,支持需求分析等高层次生存期阶段的工具更少,因此要求支持某类软件开发方法的全过程已很不容易了。如Yourdon公司的Cradle软件开发环境支持Yourdon结构化开发方法,Jackson工具集支持Jackson开发方法。

随着软件开发工具的积累,自动化工具的增多,软件开发环境进入了第三代集成计算机辅助软件工程(ICASEIntegrated Computer-AidedSoftware Engineering)。系统集成方式经历了从数据交换(早期CASE采用的集成方式:点到点的数据转换),到公共用户界面(第二代CASE:在一致的界面下调用众多不同的工具),再到目前的信息中心库方式。这是ICASE的主要集成方式。它不仅提供数据集成和控制集成,还提供了一组用户界面管理设施和一大批工具,如垂直工具集(支持软件生存期各阶段,保证生成信息的完备性和一致性)、水平工具集(用于不同的软件开发方法)以及开放工具槽。

ICASE的进一步发展则是与其它软件开发方法的结合,如与面向对象技术、软件重用技术结合,以及智能化的ICASE。近几年已出现了能实现全自动软件开发的ICASE

ICASE的最终目标是实现应用软件的全自动开发,即开发人员只要写好软件的需求规格说明书,软件开发环境就自动完成从需求分析开始的所有的软件开发工作,自动生成供用户直接使用的软件及有关文档。

在应用最成熟的数据库领域,目前已有能实现全部自动生成的应用软件,如MSE公司的Magic系统。它只要求软件开发人员填写一系列表格(相当于要求软件实现的各种功能),系统就会自动生成应用软件。它不仅能节省90%以上的软件开发和维护的工作量,而且还能将应用软件的开发工作转交给熟练的用户。

 

1.3.8软件重用和组件连接

软件重用又称软件复用或软件再用。早在1968年的NATO软件工程会议上就已提出可复用库的思想。1983年,Freeman对软件重用给出了详细的定义:在构造新的软件系统的过程中,对已存在的软件人工制品的使用技术。软件人工制品可以是源代码片断、子系统的设计结构、模块的详细设计、文档和某一方面的规范说明等。所以软件重用是利用已有的软件成份来构造新的软件。它可以大大减少软件开发所需的费用和时间,且有利于提高软件的可维护性和可靠性。目前软件重用沿着下面三个方向发展:

第一个方向是基于软件复用库的软件重用。它是一种传统的软件重用技术。这类软件开发方法要求提供软件可重用成份的模式分类和检索,且要解决如何有效地组织、标识、描述和引用这些软件成份。通常采用两种方式进行软件重用:1)第一种方式是基于生成技术的软件重用,这是对模式的重用。由软件生成器通过替换特定参数,生成抽象软件成份的具体实例。(2)第二种方式是基于组装方式的软件重用。常用的组装方式有:子程序库技术、共享接口设计和嵌套函数调用等。组装方式对软件重用成份通常不作修改,或仅作很少的修改。

第二个方向是与面向对象技术结合的软件复用。OO技术中类的聚集、实例对类的成员函数或操作的引用、子类对父类的继承等使软件的可重用性有了较大的提高。而且这种类型的重用容易实现。所以这种方式的软件重用发展较快。

第三个方向是基于组件连接的软件复用,这是目前发展最快的软件重用方式。最早的组件连接技术OLE1.0Object Linking and Embedding)是Microsoft公司于199011月在COMDEX展览会上推出的。OLE
1.0的规范发表于199012月,19912月推出了第一批支持OLE
1.0规范的应用程序。19935月发表了OLE 2.0。几个月后,第一批支持OLE 2.0的应用程序问世。

OLE给出了软件组件(ComponentObject)的接口标准。这样任何人都可以按此标准独立地开发组件和增值组件(组件上添加一些功能构成新的组件),或由若干组件组建集成软件。在这种软件开发方法中,应用系统的开发人员可以把主要精力放在应用系统本身的研究上,因为他们可在组件市场上购买所需的大部分组件。

软件组件市场组件集成方式是一种社会化的软件开发方式,因此也是软件开发方式上的一次革命,必将极大地提高软件开发的劳动生产率,而且应用软件开发周期将大大缩短,软件质量将更好,所需开发费用会进一步降低,软件维护也更容易。

软件组件连接的另一个标准是19953月推出的OpenDoc。这是IBMApple等公司组成的
CI Labs集团使用的标准。由于OpenDoc的编程接口比OLE小,因此OpenDoc的应用程序能与OLE兼容。

第三个组件连接标准是对象管理集团OMG1991年发表的CORBACommonObject
Request Broker Architecture),1994OMG又发表了CORBA 2.0

由于OLE1.0OLE 2.0的部分功能已放入Windows 3.1(在推出OLE2.0的同时,推出Windows
3.1OLE 2.0),因此目前使用的组件连接开发技术大多基于OLE 2.0

综上所述,今后的软件开发将是以OO技术为基础,可视化开发、ICASE和软件组件连接三种方式并驾齐驱。它们四个将一起形成软件界新一轮的热点技术。

 

1.4研究的意义

在现代软件发展的趋势下,程序开发方法也在不断变化,其手段越来越先进,可视话编程使得代码的编制更简单,而操作系统提供的强大的底层支持,使得程序员能够花较少的精力在输入、输出和界面设计上。而如何通过新的软件体系结构和开发方法,提高程序开发效率,适应当前软件业发展要求,则越来越多地摆在了我们面前。

首先,传统的体系结构不利于大规模的集群化开发,对现代软件产业的发展造成极大的限制。对软件体系结构和开发方法的研究,实现用新的方法划分软件开发模块和功能,使之相互之间具有更少关联,更大独立性,以及采用新的技术手段和新的开发方法,可以促进提高程序开发过程中的并行性,为软件产业的大规模生产提供支持。

其次,新的软件体系结构和开发方法将更能克服传统的体系结构和方法由于单一固定的模式导致的程序代码重用性能低,且不易对功能模块进行替换等缺点。能够在开发和升级过程中更高效地去处理新的要求和功能,节省开发时间和花费。

最后,软件体系结构和开发方法的进步也必将会促进编程方法的发展,从而提高软件产业的生产效率,因此对软件体系结构和开发放发的研究,能够改进软件开发,使得软件开发更加科学合理和高效率,促进软件产业的规模化,跟上全球信息产业发展的趋势和步伐。

本章简单介绍了软件体系结构和开发方法对软件开发的重要意义,分析了传统软件体系结构的弊端,概括了软件开发方法发展的历史并对其进行归类,对当今软件体系结构和开发方法的现状和发展方向进行了总结。对软件体系结构和开发方法进行研究,对促进软件产业发展,提高软件开发效率都具有重要的意义。在下一章中将介绍插件体系结构和以此结构为驱动进行软件开发的方法。

 


2.插件体系结构开发方法

2.1插件的概念

插件体系结构的开发方法,是一种程序设计技术,是一种面向组件的软件开发方法。在插件结构的应用系统中,程序并不是单一的执行文件,而是由主程序和若干外部模块组成。这些模块是按照一定的规则编写,可以通过配置文件灵活地加入到系统中,也可以在程序运行时动态地加入到系统中。由于可以灵活机动地增加减少替换这些模块,通常把插入到系统中的模块称为插件,基于插件的系统称为插件系统,而把这种开发方法称为插件驱动开发方法。在插件程序中,插件管理程序可以通过一定的规则和特定的接口与插件通信以及调用插件实现的功能。与一般的函数调用不同,插件管理程序与插件是相对独立的,插件管理程序可以脱离具体的插件模块而独立运行,插件也是实现特定功能的相对独立模块,插件管理程序可以在程序运行的过程中动态调用多个不同插件所提供的服务。

与传统的软件体系结构的一个程序集成所有功能不同,插件体系结构将大部分功能放在外部插件中,插件管理程序中仅仅包含组织协调和调用插件的功能,所以软件的功能就不像传统的那样固定不变。而且由于这种方法的方便和灵活性,能够通过插入和改变外部插件来实现软件功能的扩充和改进提高,这也是插件体系结构开发方法的魅力所在。图2.1是传统体系结构和插件体系结构的比较图:

2.1传统体系结构与插件体系结构

 

2.2插件体系结构开发方法的优点

与传统的软件体系结构相比,插件体系结构的程序中,插件管理程序和插件程序之间的划分是非常清楚的,这使得插件管理程序的结构简单,只负责处理与插件的通信与调用插件的功能,而与具体功能相关的数据结构算法等则由插件模块来完成,而各个插件之间几乎没有什么联系和干扰。把各个插件之间的联系和干扰降到最低,是在划分模块组织软件过程中应该尽量满足的要求,这样能够降低插件模块之间的耦合度,减少插件之间的依赖。采用这种方法,在完成软件的设计后,插件管理程序和各个插件模块可以完全独立开发,这有利于在大规模软件开发过程中,使开发能够并行进行,提高开发的效率和质量。并且在开发和调试过程中,当部分插件模块发生问题的时候,由于各个插件模块之间相对独立,并不会影响到其他模块,有利于软件测试工作的开展和进行,便于在测试和调试的过程中发现问题然后解决问题。另外,对部分插件的修改,不会影响到其他插件,避免了因为改正错误而引出新的问题的情况。

插件体系结构的开发方法,给程序开发人员带来了灵活性,在主要模块完成后就可以正式发行,不必等到整个开发过程的全部结束。在软件发布以后还可以添加新的插件和完善已有的功能。这样,大大缩短了软件的开发周期,这样可以节省出传统的软件体系结构和开发方法开发出的软件在维护阶段所需要投入的人力,物力和财力,集中力量与资源到新产品的开发当中。

同时插件甚至可以交给第三方公司开发,提高行业内的合作能力,达到优势互补的目的。插件开发完成后,也可能被其他的应用程序用到,提高了软件的可重用性。

因此,在现代软件业向规模化产业化发展的时候,插件程序体系结构和开发方法具有巨大的优势和潜力。

 

2.3插件与组件

二十世纪九十年代后,软件技术的最大进步之一就是面向组件的开发方法。这使传统应用程序的完整性和单一性向群体生产率提高的方向发展。面向组件的开发方法有利于提高软件的复用性,节省开发时间和维护成本。众多软件厂商在这方面做了努力与创新,这些新的方法大都采用了类似的做法:将程序的各个功能模块划分为不同的组件进行开发,各个组件之间独立无关,通过插件管理程序进行调度和相互通信,而功能则隐藏在组件内部来实现,对外提供稳定的外观和接口,改变组件不会影响到它的外部程序,这一类程序被称为独立组件结构的应用程序。

其中微软公司开发的COMComponent Object Model)方法是比较有影响力和普及的,以COM为基础的OLEACTIVEX等技术也在微软办公软件,浏览器软件等上面大量采用,许多开发人员也开始采用COM架构进行开发。COM方法将应用程序分为容器程序以及多个组件程序,容器程序可以访问组件程序所提供的服务功能。总之,组件式的应用程序与插件式程序特别是结构上有些相似,但也有很多不同之处。组件式应用程序更像是由固定的组件所组成的应用程序,而功能也比较固定,而插件程序中插件的数量并不固定,在资源许可的情况下可以插入任意数量的插件,因此程序的功能也可以增加。插件式结构在应用中更加灵活,不像组件式应用程序需要大量地考虑兼容性和可重用性的问题,这使得它在应用中对程序会有许多额外的要求,比如COM要利用WINDOWS的注册标机制。

2.2插件和组件

因此插件和组件从设计目的来看是有很大的不同的,组件是为了提高复用,易于对部分组件进行替换和升级,方便网络上不同的客户通过同样的接口访问等等。而插件则倾向于在程序中插入大量的插件来增强系统功能。图2.2是组件和插件结构的比较图。

 

2.4插件程序的应用领域

插件式程序在当前有着广泛的应用前景,特别是大规模的软件开发当中,对于提高群体的开发效率,缩短开发周期以及降低设计难度有着相当突出的作用。插件程序的难点在于设计插件模块程序和插件管理程序之间的借口,通过接口主程序能够正确调用插件中的功能。因此当一个程序中需要大量相似的功能的时候,我们可以把这些功能设计为插件结构,并且通过同样的接口与插件管理程序相连,从而达到充分利用人手,使开发和测试都能够并行进行。另外,对于软件中可能会经常发生变化的部分,或者一时难以全部开发完成的部分,也可以用插件的方式来实现,以便于将来增加新的功能和修改已有功能。

事实上,只要设计得当,一个软件几乎可以全部都利用插件来完成,组成它的每一个菜单,按钮,对话框和功能模块都可以是插件,插件在实际应用中是可以多种多样的,除了可执行代码外,还可以是图像生音动画等。因此插件式体系结构和以其驱动的开发方法能够在大量应用中采用,并成为一种重要的开发方法。有非常成功的流行的软件采用此种方法来开发和设计,例如WinampPhotoShop,采用Java开发的Eclipse软件和采用C#开发的SharpDevelop软件等。当然根据软件任务功能特点的不同,采用的插件结构形式也不尽相同。

 

2.5插件程序的发展方向

随着技术的进步,以及产业发展的要求传统的单一模块的软件体系结构和开发方法必然会被面向组件的开发方法所取代。插件结构作为一种重要的形式,在应用中有着灵活方便的好处,在设计上也有利于理解的特点。但是目前很多时候插件程序很多还仅仅限于较小的应用,仅仅作为对界面,声音以及众多比较简单功能的补充,还没有完全发挥出插件程序的优势。插件式体系结构的开发方法还有很多的潜力可开发和挖掘。

在今后的应用中,可以考虑让越来越多越来越复杂软件采用插件驱动的开发方法来进行开发。例如我们可以设计出一个软件由众多的插件模块构成,而每一个模块也还可以由更小的插件结构构成,从而降低开发难度,提高开发速度,加快开发进度。并且在计算机网络日益普及的情况下,面向网络的应用也越来越多,插件结构需要能够实现跨计算机边界跨平台的应用。同时为了能够在复杂或者多用户的环境下应用,插件结构也必须解决在各种情况下出现的多样需求问题,比如并行性需求或者可重入性需求。总之插件式结构的发展是无止境的。

本章引入了软件体系结构中一个重要分支插件结构的概念,引入了以插件为驱动进行开发的软件开发方法。分析了其体系结构和开发方法的特点以及在软件开发中的主要优点,并且与面向组件开发方法中的与插件式相似的组件式体系结构进行了比较,然后从它的应用领域发展方向方面进行了讨论。本章的重点在于介绍插件式体系结构和开发方法,以及它的优点和为开发所带来的好处,突出当前研究它的重要意义与价值所在。在下一章中将着重介绍基于.NET的插件体系结构软件开发方法中用到的技术。

 


3.基于.NET的插件体系结构总述

3.1插件结构的分析

3.1.1插件结构

插件结构的程序在形式上分为独立的插件管理程序和多个互不相关的插件,各部分能够共同形成一个逻辑上的完整系统。插件可以看作是对系统功能的补充,通过加入不同的插件,系统可以获得不同的能力。图3.1是插件体系结构示意图。

3.1插件体系结构

在这里我们所要研究的插件结构中,插件框架主程序只完成很少的一部分功能,初始化系统环境,调度插件运行等,大部分功能将由插件来完成。这种方式的好处在于,主程序能够相对简单,系统的大部分功能可以划分为最小的独立功能模块来分别开发,提高开发过程的并行性。在实际运行过程中,主程序将插件插入到系统中,并可以获得插件提供的服务。在这样的系统中,应该包含以下几个部分:

插件管理程序:完成基本的系统功能,可以插入不同的插件,接受插件提供的服务并提供给用户,是整个系统的基础和主干。

插件:能够动态地插入到系统中,提供给插件系统相对简单的功能,但是多个插件能够使系统功能完善,完成许多复杂处理,是插件系统的重要构成部分。

接口:独立的主程序和插件能够互相结合在一起工作,必须有一套互相协作的规则和协议来使不同来源的这些程序互相协调工作。完成这些规则和协议的部分称为插件系统的接口。这是一个逻辑上的接口,由主程序和插件各完成一部分,它完成插件的插入,调用,中止插件的服务,主程序与插件以及插件与插件之间的交互,是插件系统中的重要组成部分。

 

3.1.2研究插件结构的目的

首先,插件结构提供多样性的服务。如果设计得当,一个应用系统的大部分或者全部部分都可以用插件的形式来实现,一个系统能够通过插入不同的插件而变成功能不同的另外一个系统。目前已经有很多应用采用这种办法来进行开发。我们可以让插件提供从系统图形交互界面到基本处理能力的所有方面的服务功能。

另外,插件服务还具有灵活性的特点。因为在一个系统的设计和开发过程中,为了使设计和开发过程简单容易,我们通常会设计尽可能功能细分的插件,使它仅接受较为简单的插件服务。但这样会大大限制插件系统的功能和应用,为了解决这个问题,需要让主程序能够同时调用多个提供简单功能的插件,让他们共同完成较为复杂的处理。为了达到这些目的,对插件系统,以及接口的设计都有较高的要求,这些部分将会在后面的章节中详细讨论。

 

3.2基于.NET的插件体系结构技术基础

3.2.1微软的.NET技术

..NET的基本概念

20006月微软公司正式推出了其下一代计算计划Microsoft.NET,这项计划将使微软现有的软件在Web
时代不仅适用于传统的PC
,而且也能够满足目前呈强劲增长势头的新设备诸如蜂窝电话以及个人数字助理(
Personal Digital Assistant PDA)等的需要。微软还计划通过创建新的工具来吸引软件开发人员和合作伙伴对.NET
的认同并且开发出其他基于Internet
的服务。

.NET
首先是一个开发平台,它定义了一种公用语言子集
(CommonLanguage Subset, CLS),这是一种为符合其规范的语言与类库之间提供无缝集成的混合语言。.NET统一了编程类库提供了对下一代网络通信标准可扩展标记语言(ExtensibleMarkup Language
XML)的完全支持使应用程序的开发变得更容易更简单。.NET计划还将实现人机交互方面的革命。微软将在其软件中添加手写和语音识别的功能,让人们能够与计算机进行更好的交流,并在此基础上继续扩展功能,增加对各种用户终端的支持能力。最为重要的.NET将改变因特网的行为方式,软件将变成为服务。与微软的其它产品一样.NETWindows
平台紧密集成并且与其它微软产品相比它更进一步。由于其运行库已经与操作系统融合在了一起,从广义上把它称为一个运行库也不为过。

简而言之.NET是一种面向网络,支持各种用户终端的开发平台环境。微软的宏伟目标是让.NET彻底改变软件的开发方式发行方式使用方式等等并且不止是针对微软一家而是面向所有开发商与运营商。.NET的核心内容之一就是要搭建第三代因特网平台。这个网络平台将解决网站之间的协同合作问题,从而最大限度地获取信息。在.NET平台上不同网站之间通过相关的协定联系在一起网站之间形成自动交流协同工作提供最全面的服务。

..NET的用途

人们的需要总是无法满足,我们不断地问自己,我们还应该有些什么。需求推动着技术的进步。在二十一世纪Internet将成为商业活动的主要场所,B2BB2C
等电子商务的运作方式,一对一营销的经营概念将网络的服务功能提高到了前所未有的程度。微软公司在此时提出.NET有其深远的战略考虑。微软公司感觉到只靠销售软件包的商务模型没有什么前途,该公司打算今后将中心转移到可以在网络上使用“服务”型商务。这样首要的问题就是解决网络上用来开发并执行“服务”的平台。这就是.NET提高软件开发生产效率并且试图使应用软件的发布更为容易,再也不想因为DLL版本不同而烦恼,希望不用重新启动电脑就能够安装应用软件,改进用户界面,并能支持多种用户终端用户界面。演进的结果包括两方面的内容,一是完成传统的PC界面与基于XML
的浏览器界面间的过渡,二是对自然语言和语音识别的支持,从而使用户与各种终端之间的沟通更加透明,真正达到网络互连的3AAnywhere Anytime Any device

今天许多的人时常问除了上网看新闻我们究竟还能干什么?这是因为今天的互联网与旧式的大型计算机的工作模式还有许多相似之处,信息被储存在中央服务器内,而用户的所有操作都要依靠它们让不同的网址之间相互传递有意义的信息。或者合作提供更广泛和更深层次的服务还是一件十分困难的事。现代人时常有一种困惑感觉到如今生活在技术与机器架构的丛林中,我们在努力地去适应机器适应技术。适应人类科技以人为本还只是一个美好的愿望,这是因为我们还不能将控制信息的权利交给那些需要信息的人们。.NET的出现意味着人们可以只用一种简单的界面就可以编写浏览编辑和分享信息,而且还可以得到功能强大的信息管理工具。由于使用的所有的文件都以符合网络协议的格式存在,所以所有的商业用户和个人用户都可以方便地查找和使用其中的信息。任何规模的公司都可以使用相同的工具与他们的供应商商业伙伴和客户高效地沟通和分享信息。这样就创造出一种全新的协同工作模式,总之.NET战略是一场软件革命。

.NET
对最终用户来说非常重要,因为计算机的功能将会得到大幅度提升,同时计算机操作也会变得非常简单。特别地用户将完全摆脱人为的硬件束缚,用户可以自由冲浪于因特网的多维时空自由访问自由查看自由使用自己的数据而不是束缚在便携式电脑的方寸空间——可通过任何桌面系统任何便携式电脑任何移动电话或
PDA进行访问,并可对其进行跨应用程序的集成。

.NET
对开发人员来说也十分重要,因为它不但会改变开发人员开发应用程序的方式而且使得开发人员能创建出全新的各种应用程序,大幅提高软件生产率。
.NET将保证完全消除当今计算技术中的所有缺陷,.NET
定能实现确保用户从任何地点任何设备都可访问其个人数据和应用程序的宏伟蓝图。

.NET
把雇员客户和商务应用程序整和成一个协调的能进行智能交互的整体,而各公司无疑将是这场效率和生产力革命的最大受益者。
.NET承诺为人类创造一个消除任何鸿沟的商务世界。

..NET的核心组件

.NET
的核心组件包括一组用于创建互联网操作系统的构建块,其中包括
Passport.NET,用于用户认证以及用于文件存储的服务。用户首选项管理日历管理以及众多的其它任务。构建和管理新一代服务的基本结构和工具包括VisualStudio.NET.NET
业服务器,.Net Framework
Windows.NET,能够启用新型智能互联网设备的.NET设备软件。.

..NET的用户体验

.NET框架支持多种编程语言。回顾一下近十年来软件开发的历史,多年以前当微软的组件对象模型(ComponentObject Model, COM)尚未推出时,软件的复用性对于开发人员仅仅是一种美好的憧憬。成千上万的程序员为了处理通信接口和不同语言间的冲突而通宵达旦地艰辛劳动但却收效甚微。COM的出现改变了这一切。通过将组件改变为通用集成型的构件,开发人员正逐渐地从过去的繁复编程事务中解脱出来,可以选择自己最得心应手的编程语言进行编程。然而软件组件与应用程序之间的联合仍然是松散的,不同的编程语言与开发平台限制了部件间的互用性,其结果是产生了日益庞大的应用程序与不断升级的软硬件系统。举个很简单的例子只用五行C语言代码就能编写出的一个简单程序,若使用COM
来编写,结果会是令人吃惊的我们需要几百行代码。COM在带来巨大价值的同时也大大增加了开发开销,而.NETFramework
的出现使得一切问题都迎刃而解。实际上在.NETFramework中所有的编程语言从相对简单的JScript到复杂的C++语言一律是等同的。Framework框架是开发人员对编程语言命令集的称呼。.Net框架的意义就在于只用统一的命令集支持任何的编程语言。.NET的作用不仅仅是将开发人员从必须掌握多种框架的束缚中解脱出来,通过创建跨编程语言的公共API集,.NET
框架可提供强大的跨语言继承性,错误处理和调试功能。现在开发人员可以自由地选择他们喜欢的编程语言。.NET将使编程人员梦想的语言互用性变成为近在眼前的现实。想想看一个在VisualBasic中定义的类能够在另一种与它完全不同的语言环境中使用调试甚至继承,这是多么令人兴奋的事情。.NET框架是.NET平台的基础架构,其强大功能来自于公共语言运行时(Common
Language Runtime,CLR),虚拟执行系统,下面分别对它们进行简要介绍。

.NET
跨语言集成的特性来自于虚拟对象系统(
VOS)的支持。在不同语言间进行代码复用和应用集成中所遇到的最大问题是不同语言类型系统间的相容性问题。可以想象不同的语言虽然语法结构大体相同,但数据类型与语言环境本身的各种特点联系紧密,很难想象一种解释性的语言所拥有的数据类型会与一种编译语言相同。而即使相同的数据类型在不同的语言环境中表示的意义也存在差别。例如同样是整数类型在MSSQL中的长度是32位而在VB中却是16位。至
于日期时间与字符串类型在这方面的区别就更加明显了。VOS的建立就是为了改变这种状况,它既支持过程性语言也支持面向对象的语言。同时提供了一个类型丰富的系统来容纳它所支持的各种语言的特性,它在最大程度上屏蔽了不同语言类型系统间的转换,使程序员能够随心所欲地选择自己喜欢的语言。当然这种语言必须支持.NET。应用从事开发保证了不同语言间的集成,对于过程性语言它描述了值的类型并指定了类型的所有值必须遵守的规则,在面向对象的语言方面它统一了不同编程语言的对象模型。每一个对象在VOS中都被唯一标识以与其它对象相区别。

元数据是对VOS中类型描述代码的一种称呼,在编译程序将源代码转换成为中间代码时它将自动生成并与编译后的源代码共同包含在二进制代码文件中。元数据携带了源代码中类型信息的描述,这在一定程度上解决了版本问题。程序使用的类型描述与其自身绑定在一起,在CLR定位与装载类型时系统通过读取并解析元数据来获得应用程序中的类型信息。JIT编译器获得加载的类型信息后将中间语言代码翻译成为本地代码,在此基础上根据程序或用户要求建立类型的实例。由于整个过程中CLR始终根据元数据建立并管理对应特定应用程序的类型从而保证了类型安全性。此外元数据在解决方法的调用建立运行期上下文界限等方面都有着自己的作用,而关于元数据的一切都由.NET在后台完成。

公用语言规范(CommonLanguage SpecificationCLS)是CLR
定义的语言特性集合,主要用来解决互操作问题。如果一个类库遵守CLS,那么同样遵守CLS规范的其它编程语言将能够使用它的外部可见项。

虚拟执行系统(VisualExecution SystemVES)是VOS
的实现,它用来驱动运行环境元数据的生成与使用。公用语言规范的满足性检查以及应用程序执行过程中的内存管理均由它来完成。具体说来VES主要完成以下功能:装入中间代码,使用JIT将中间代码转换为本地码,装入元数据,代码管理服务,包括垃圾收集器和异常处理,定制与调试服务线程和环境管理。

.公用语言运行时环境与公用语言规范

了解了.NET的结构之后我们该看看.NET利用其结构为我们创造的运行环境,公用语言运行时环境。它是C#及其它支持.NET平台的开发工具的运行基础。具体来说它为我们的应用提供了以下益处,跨语言集成的能力,跨语言异常处理,内存管理自动化,强化的安全措施,版本处理技术,组件交互的简化模型。

.NET提供了一个运行时环境,叫做公用语言运行时。它管理着代码的执行并使得开发过程变得更加简单,这是一种可操控的执行环境。其功能通过编译器与其它工具共同展现你的代码,将受益于这一环境。依靠一种以运行时为目标的指完全支持运行时环境的编译器所开发的代码叫做可操控代码,它得益于可操控环境的各种特性。跨语言集成,跨语言异常处理增强的安全性版本处理与开发支持简单的组件交互模型以及调试服务。为了使运行时环境能够向可操控代码提供服务,语言编译器需要产生一种元数据,它将提供在你使用语言中的类型成员引用的信息。元数据与代码一起存储,每个可加载的CLR映像均包含了元数据,运行时环境使用元数据定位并载入类,在内存中展开对象实例解决方法调用,产生本地代码强制执行安全性并建立运行时环境的边界。运行时环境自动处理对象的展开与引用,当它们不再使用时负责它们的释放。被运行时环境进行这样的生命期管理的对象被称为可操控代码。自动内存管理消除了内存溢出,同时也解决了其它一些常见的语法错误,如果你的代码是可操控的,你仍然可以在需要的时候使用非可控代码或者在你的.NET应用中同时使用可控与非可控代码。由于语言编译器支持他们自己的类型,比如一些原始类型,你可能并不总是知道,也不必知道你的数据是否是可控的。CLR使设计跨语言的组件与应用变得更加容易。以不同语言设计的对象能够彼此间进行通信,并且它们的行为能够紧密地综合与协调。举个例子你定义了一个类,然后可以在另一种不同的语言中从该类中派生了一个类,或者调用它其中的一个方法,你也可以向另一种语言中类的方法传递该类的一个实例,这种跨语言的集成之所以可能,因为以运行时间为目标的语言编译器与工具使用一种运行时间所定义的公用类型系统,他们遵守运行时的规则公用语言规范来定义新的类型,生成使用保持并绑定类型作为元数据的一部分。所有可控组件携带了关于它们所依赖的组件与资源的信息。运行时环境使用这些信息来保证你的组件或应用具有需要的所有东西的特定版本,其结果是你的代码将不会因为版本冲突而崩溃。注册信息与状态数据不再保存,在难以建立与维护的注册表中你所定义的类型及附属信息作为元数据被保存,这使得复制与移动组件的复杂程度得到降低。编译工具用他们自己的方式向开发人员展现CLR的功能。这意味着运行时间的一些特性可能在不同的语言中的表现形式将会有所不同。你怎样体验运行时的特性将取决于你所使用的语言,比如说如果你是一位VB开发人员你可能注意到在运行时环境的帮助下VB语言比以前具有更多的面向对象的特性。

前面的叙述中我们多次提到了可操控这一概念,这意味着它指向的对象在执行过程中完全被运行时环境所控制。在执行过程中运行时环境提供以下服务,自动内存管理,调试,支持增强的安全性及与非可操控代码的互操作性,例如COM组件。在可控执行进程中的第一步是选择源代码的生成工具,如果你希望你的应用拥有CLR提供的优势,你必须使用一种或多种以运行时为目标的语言编译器,例如VBC#VC的编译器,或者一种第三方编译器如PERLCOBOL编译器。由于运行时是一种多语言执行环境它支持众多的数据类型和语言特性。你使用的语言编译器决定你将使用运行时的哪一部分功能子集,在代码中使用的语法由你的编译器决定,而不是运行时环境。如果你的组件需要被其他语言的组件完全使用,那么你必须在你组件的输出类型中使用CLR所要求的语言特征。当你完成并编译你的代码时,编译器将它转换为微软中间语言(MicrosoftIntermediate
LanguageMSIL),同时产生元数据。当你要执行你的代码时,这种中间语言被即时(JustIn TimeJIT
编译器编译成为本地代码。如果安全策略需要的代码是类型安全的,通常情况下都是如此。JIT
编译器将在编译进程中对中间语言进行类型检查,一旦失败在代码执行中将会触发异常。

 

3.2.2 XML技术

XML代表ExtensibleMarkup Language,意为可扩展的标记语言。1996年,万维网协会(W3C)开始设计一种可扩展的标记语言,使其能够将SGML的灵活性和强大功能与已经被广泛采用的HTML结合起来。这种后来变成XML
的语言继承了SGML的规范,而且实际上就是后者的一个子集。从SGML入手使得该设计小组能够将精力集中在简化已有的成果上。SGML
已经提供了一种可以无限扩展的语言,它允许任何人能够根据自己的需要加以扩充。XML之所以要较SGML更为简化,很大程度上是出于易用性的考虑:人们对标记的读写过程应该使用现有的、简便通用的工具,同时,我们也应当简化计算机对文档和数据交换的处理。由于有太多的可选功能,SGML变得过于复杂,以至于很难编写出针对这种语言的普通解释器,而XML的解释器则简单得多。此外,XML使得现有的Internet协议和软件更为协调,从而简化了数据处理和传输。作为一个不错的SGML子集,XML还保持了对现有的面向SGML的系统的向下兼容性,这样,用XML标记过的数据就仍然可以在这些系统中使用,为基于SGML的行业节省了大笔的改造费用,同时,与We
b的结合也使得它们更便于被访问。19982月,XML 1.0
成为了W3C的推荐标准。X M L
是一种界定文本数据的简便而标准的方法。它曾经被人称作
We b
上的
A S C I I 码”。就好像你可以使用自己喜爱的编程语言来创建任何一种数据结构,然后同其他人在其他计算平台上使用的其他语言来共享一样。XM L
的标记用来说明你所描述的概念,而属性则用来控制它们的结构。所以,你可以定义自己所设计出的语法并同其他人共享。

HTML相似,XML是一种显示数据的标记语言,它能使数据通过网络无障碍地进行传输,并显示在用户的浏览器上。XML是一套定义语义标记的规则,这些标记将文档分成许多部件并对这些部件加以标识。它也是元标记语言,即定义了用于定义其他与特定领域有关的、语义的、结构化的标记语言的句法语言。

关于XML要理解的第一件事是,它不只是像超文本标记语言(HyperTextMarkup LanguageHTML)或是格式化的程序。这些语言定义了一套固定的标记,用来描述一定数目的元素。如果标记语言中没有所需的标记,用户也就没有办法了。这时只好等待标记语言的下一个版本,希望在新版本中能够包括所需的标记,但是这样一来就得依赖于软件开发商的选择了。

但是XML是一种元标记语言。用户可以定义自己需要的标记。这些标记必须根据某些通用的原理来创建,但是在标记的意义上,也具有相当的灵活性。例如,假如用户正在处理与家谱有关的事情,需要描述人的出生、死亡、埋葬地、家庭、结婚、离婚等,这就必须创建用于每项的标记。新创建的标记可在文档类型定义(DocumentType
DefinitionDTD)中加以描述。在本书的第二部分中将会学到有关DTD的更多的知识。现在,只需把DTD看作是一本词汇表和某类文档的句法。

XML定义了一套元句法,与特定领域有关的标记语言都必须遵守。如果一个应用程序可以理解这一元句法,那么它也就自动地能够理解所有的由此元语言建立起来的语言。浏览器不必事先了解多种不同的标记语言使用的每个标记。事实是,浏览器在读入文档或是它的DTD时才了解了给定文档使用的标记。关于如何显示这些标记的内容的详细指令是附加在文档上的另外的样式单提供的。有了XML就意味着不必等待浏览器的开发商来满足用户的需要了。用户可以创建自己需要的标记,当需要时,告诉浏览器如何显示这些标记就可以了。XML标记描述的是文档的结构和意义。它不描述页面元素的格式化。可用样式单为文档增加格式化信息。文档本身只说明文档包括什么标记,而不是说明文档看起来是什么样的。

XML的特点:使用有意义的标记(TAG),数据的语义与显示方式分开,可自定义的标记,严格的语法控制。

XML最大的优势在于对各种数据的管理。任何系统都可以通过XML的解析器来读取XML数据,因此它的数据可以通行各处,而不用担心系统不支持的问题。

XML的应用主要有:内容管理(ContentManagement)、电子邮件的收发与管理、智能型日历、个性化信息服务、电子商务。

 

3.2.3面向对象的程序设计方法

面向对象方法会在开发和设计中带来极大的好处,设计人员可以把每个插入到系统中的插件用对象方法封装起来,从而可以充分利用面向对象方法的优势,采用它的封装、继承、抽象等特性,使得应用程序和插件之间的交互,通信,调用时的关系变得清晰和简单,并能够简化其间所需要的复杂的数据结构,减少彼此间的相关性和干扰,最大限度地降低设计和开发难度。面向对象方法能够增进生产效率,提高质量,加强可维护性。

 

3.2.4设计模式

设计面向对象软件比较困难,而设计可复用的面向对象软件就更加困难。你必须找到相关的对象,以适当的粒度将它们归类,再定义类的接口和继承层次,建立对象之间的基本关系。你的设计应该对手头的问题有针对性,同时对将来的问题和需求也要有足够的通用性。你也希望避免重复设计或尽可能少做重复设计。有经验的面向对象设计者会告诉你,要一下子就得到复用性和灵活性好的设计,即使不是不可能的至少也是非常困难的。一个设计在最终完成之前常要被复用好几次,而且每一次都有所修改。

有经验的面向对象设计者的确能做出良好的设计,而新手则面对众多选择无从下手,总是求助于以前使用过的非面向对象技术。新手需要花费较长时间领会良好的面向对象设计是怎么回事。有经验的设计者显然知道一些新手所不知道的东西。

内行的设计者知道:不是解决任何问题都要从头做起。他们更愿意复用以前使用过的解决方案。当找到一个好的解决方案,他们会一遍又一遍地使用。这些经验是他们成为内行的部分原因。因此,你会在许多面向对象系统中看到类和相互通信的对象的重复模式。这些模式解决特定的设计问题,使面向对象设计更灵活、优雅,最终复用性更好。它们帮助设计者将新的设计建立在以往工作的基础上,复用以往成功的设计方案。一个熟悉这些模式的设计者不需要再去发现它们,而能够立即将它们应用于设计问题中。

一旦懂得了模式,许多设计决策自然而然就产生了。我们都知道设计经验的重要价值。你曾经多少次有过这种感觉——你已经解决过了一个问题但就是不能确切知道是在什么地方或怎么解决的?如果你能记起以前问题的细节和怎么解决它的,你就可以复用以前的经验而不需要重新发现它。然而,我们并没有很好记录下可供他人使用的软件设计经验。

设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。设计模式帮助你做出有利于系统复用的选择,避免设计损害了系统复用性。通过提供一个显式类和对象作用关系以及它们之间潜在联系的说明规范,设计模式甚至能够提高已有系统的文档管理和系统维护的有效性。简而言之,设计模式可以帮助设计者更快更好地完成系统设计。

设计模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。

一般而言,一个模式有四个基本要素:

首先是模式名称。一个助记名,它用一两个词来描述模式的问题、解决方案和效果。命名一个新的模式增加了我们的设计词汇。设计模式允许我们在较高的抽象层次上进行设计。基于一个模式词汇表,我们自己以及同事之间就可以讨论模式并在编写文档时使用它们。模式名可以帮助我们思考,便于我们与其他人交流设计思想及设计结果。找到恰当的模式名也是我们设计模式编目工作的难点之一。

然后是问题。描述了应该在何时使用模式。它解释了设计问题和问题存在的前因后果,它可能描述了特定的设计问题,如怎样用对象表示算法等。也可能描述了导致不灵活设计的类或对象结构。有时候,问题部分会包括使用模式必须满足的一系列先决条件。

解决方案(Solution)描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。

最后是效果。描述了模式应用的效果及使用模式应权衡的问题。尽管我们描述设计决策时,并不总提到模式效果,但它们对于评价设计选择和理解使用模式的代价及好处具有重要意义。软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植性的影响。一个设计模式命名、抽象和确定了一个通用设计结构的主要方面,这些设计结构能被用来构造可复用的面向对象设计。设计模式确定了所包含的类和实例,它们的角色、协作方式以及职责分配。每一个设计模式都集中于一个特定的面向对象设计问题或设计要点,描述了什么时候使用它,在另一些设计约束条件下是否还能使用,以及使用的效果和如何取舍。

 

3.3插件体系结构的接口

3.3.1插件体系结构接口概述

接口是可插入系统中重要的组成部分之一,这对所有的插件式程序都是如此。在图3.2中,右图更能清楚地表明插件之间的关系,因此,插件的接口看起来比插件本身更重要,在插件系统中,插件的接口起着同样重要的作用。

3.2接口与组件的关系

如前面章节所述,插件式程序由插件系统程序和若干功能插件组成,插件可以动态地加入到应用系统中。在这种结构的系统中,插件与插入系统之间的接口对用户来说是透明的,插件中所提供的功能与系统程序所提供的功能并没有什么不同。但是在实际的应用中,我们往往不能确定今后开发的插件会提供什么样的服务,特别是当很多插件依赖第三方来开发的时候尤其如此。因此,对于一个设计得良好的插件系统,我们希望它能够在几乎不知道插件功能的情况下,识别插件,并且能够正确调用插件所提供的服务。那么插件和应用系统之间应该有一层良好交互的接口,这种接口在设计不同的应用系统时,就根据各自的需要进行规划,并应该能满足以下要求:1.应该能够识别出所有按照此接口规范进行设计的插件,并将其插入到插件系统中。2.插入系统程序和插件都能够正确实现按照接口规范进行功能调用,而不管提供这种调用的主程序和插件是什么。3.不同的插件在插入到同一个系统中的时候能够协调共同工作,互不干扰。4.动态插入和删除插件不影响系统的正常工作。这种思想在以前计算机系统中就曾经存在过,现在很多操作系统的硬件管理就是采用这种方式实现的,能够动态识别插入到系统中的硬件并且正确进行处理和采用合适的驱动程序。

 

3.3.2插件接口的特点

这种体系结构与一般意义的分布式组件程序不同,它不是由固定的组件来实现完整的功能,相反,除了系统最初提供的实现基本功能的插件外,它没有固定需要的插件,插入到系统中的插件数量也并不固定,一切视实际的系统环境和需求来确定。因为在这样的体系结构中,系统的大部分功能都依赖于插入到系统中的插件所提供的服务,所以在每一个实际运行的插件系统中,都可能根据自身的实际需要而采用了不同的插件,从而具有不同的功能,这种功能可以是界面、操作方式、处理的数据以及所有一个软件所能够表现出来的样子,这就决定了插件系统可以灵活多样地满足用户的不同需求。

可以想见在这样的插件系统中,插件管理程序负责为插件分配资源,创建运行环境,按照用户的要求对插件进行调用,系统与插件之间通信并交换数据,所有的插件都挂在这个系统上进行工作,为了使这些能够有条不紊地进行工作,需要为如何调用插件,如何交换数据定义一整套规则,使得插件系统能够在这些规则的指导下能够正常运行,这些规则称之为接口。通过这些接口插入到系统中的插件数量是没有限制的,那么如何让如此众多的插件协调工作成了接口的主要任务,通过以上分析,一个优秀的插件系统的接口应具有下面一些特点:

.智能化识别插件

系统中可能会插入许多插件,而这些插件都需要在系统完成后在用户端正确地动态地插入或去除,而其中的一部分还可能是第三方开发的,因而它们的功能和需要的资源都不能够事先预知,那么接口就必须在插件插入到系统中的时候对插件进行识别。这种识别包括获得插件信息,判断它是否与当前接口吻合,以决定是否将它插入到系统当中,还要从获得的信息当中获取插件所需要的资源,并为它创建运行环境,使得插件能够正确地调用。

.自动化调用

在对插件进行初始化和识别之后,系统就可以调用插件中存在的服务了,对插件的调用通常有两种方式:事件激活与自动调用。事件激活指将某种特定的事件消息传递给插件,插件调用相应的事件处理程序。自动调用则是系统可以根据自身的需要或者用户的需要对插件提供的服务进行调用。接口的自动调用能力可以使系统的插件与插件管理程序结合得更紧密,使系统能够灵活地适应各种不同的需求。

.插件管理程序与插件之间的通信

这是接口所要完成的主要功能。系统对插件的调用以及使多个插件在系统中协调地工作都是通过相互间通信来完成的。它对于多个插件的并行调用都有很大的影响。

.接口的兼容性与控制管理

一般来说对于不同的插件系统我们可能设计不同的接口,但有时新的接口只是在以前接口的基础上增加了新的能力,我们希望这种新的接口的插件也能支持以前接口的调用,这就需要新的接口能够兼容以前的版本。同时也必须对接口的版本进行管理控制,以使调用的时候不发生混乱,以及今后的开发中能方便地实现对以前接口的兼容。对于上面的这些问题在后面的章节中将会有具体的论述。

 

3.4接口的智能化识别

3.4.1接口规划与数据结构

在插件插入到系统以前,需要判断插件是否提供对当前接口的支持,如是,则取出插件的资源需求等信息对插件进行初始化,并将插件插入到系统中。因此这个过程必需满足以下要求才能保证插件系统的正确:能够监测插件是否支持接口,能够取得足够的插件信息以便能够正确调用插件提供的服务。在前面已经提到,由于插件的插入是不可事先预知的,而接口则在系统设计的时候就已经确定,那么在设计接口时候就应该为接口和插件通信定义一套完备而严格的规则,同时定义出通用的数据结构,以便让为本接口设计的插件能够通过它将插件信息提供给插件管理程序。为了制定通信规则与数据结构,我们先来看需要解决的问题:首先,由于系统支持多个插件同时插入,必须能够识别出不同的插件,正确地分辩和调用它们,特别是在一个应用插件系统中用某种插件处理过的文件,转移到另一没有安装这种插件的系统中的时候,必须防止出现差错;另外,如果系统存在升级换代的情况,必须对接口的兼容性进行处理,防止链接入不同接口的插件发生错误,对其中涉及到插件接口管理控制部分将在后面具体论述。

通过上面的分析,我们可以大致确定一个插件在插入到系统中的时候需要提供的信息,并初步设计出设计结构:数据中必须包含一个插件标识,这个标识在所有支持这个接口以及其兼容接口的插件中必须是独一无二的,这个标识可以是任意数据类型,只要能够满足唯一性的要求。同时数据中应该有插件的名称,这个名称最好能够比较简介地描述出插件的用途。另外还可以设计一系列说明性信息,比如插件的版本,生产商信息,设计日期等。根据插件系统的根本要求,接口中应该能够预留数据,以便插件能够提供其在系统中插入的位置信息,比如菜单,工具栏,按钮等。基本大致上有了这些信息后,就可以将插件插入到应用系统中了。

 

3.4.2智能化识别的过程

通过上一节对接口和数据结构的综合论述,我们再详细讨论如何对插件进行智能化识别。在识别插件的过程中,最重要的是提供插件信息给系统,传递信息的方式有很多,较为简单的方式将这些信息放入到一个文件中,插件系统在运行时读出这些信息,不过必须要确保这些信息与插件的插入和删除相一致,因此在安装插件时应有一个安装程序将这些信息加入到插件应用系统中。具体的识别过程可分为以下几个步骤:

.找到插件的模块文件,并根据设计好的接口对模块程序进行查询,看是否是合格的符合要求的插件。

.对插件进行初始化,将插件的标识号转化为本系统中插入的插件标识号,建立映射关系,为进一步调用插件提供的其他服务做好准备。

 

3.4.3识别过程中应注意的问题及解决的办法

识别插件这个过程会取得大量关于插件的信息,接口如何安排这些数据以及怎样插入插件,会对整个系统有很大的影响,这主要涉及到以下问题:

资源分配策略。插件运行过程中,根据系统的处理要求,会遇到大量的资源分配问题,其中有系统在插入系统初始化时候的资源需求,也有插件服务程序调用时候的分配资源,在一个插入大量插件,并且可以并行运行插件的系统中,可能会出现的资源分配是非常巨大的,而且可能会因为资源分配失败而导致出错,所以我们要很好地处理资源分配问题,采用合适的资源分配策略。

资源的互斥。在插件系统中,我们希望外部插件能够提供大量的功能,从而使得系统的能力更加丰富,这些能力可以是对系统文件、声音、图像、界面等各种属性的处理。但在一个提供插件并行和单个插件重入运行的系统中,可能会有两个以上插件同时对同一资源进行操作,而相互之间又是互不相容的,这就需要在识别插件的过程中,取得插件的处理类型及资源是否独占等信息,防止这些插件并行运行时候的互斥资源访问冲突问题。

 

3.5插件的自动化调用

3.5.1插件管理程序对插件的调用

当插件被应用系统识别并且插入到应用系统之中后,系统可以根据自身的需要或者自身的要求对插件提供的功能进行调用。这种调用根据我们上面所述可以分为几种类型:对插件消息的响应调用,对插件中提供的服务功能的调用,释放插件调用。对不同的情况应分别进行处理,但基本过程大致相同。

 

3.5.2调用过程中的参数问题

与所有函数调用相同,插件管理程序对插件的调用也涉及到调用参数的传递问题。与一般的函数调用不同,同一模块内的函数调用,其参数传递在编制程序时候通过静态链接就已经确定。而插件管理程序对插件的调用,很多参数在调用时候才根据实际情况确定。这样,大多数插件与插件管理程序分别开发,甚至可能是第三方开发的系统中,如果未能很好地协调一致,就可能因为参数传递出错而导致调用出错,致使系统失败。因此在插件系统设计时,必须考虑如何处理参数传递的问题。

在设计参数传递方案时,除了考虑系统正确稳定之外,还要尽量简单方便,不使今后的插件开发中难于处理,也不能影响到整个系统的运行效率。因此我们首先要考虑在插件系统中根据不同插件接口的希望插件提供的服务功能,可能会出现哪些调用。

因此,考虑到插件系统中的每一个插件,我们希望它提供的是较为简单的功能,而许多这样的插件来使得系统的功能复杂完善,那么在设计插件的接口时,应该尽量使插件的功能简单明确,从而达到使调用插件的参数尽可能简单的目的。并通过在设计中对插入到系统中的可能的插件进行分析,对通过它传递的参数进行归整化和预定义,使各种参数情况能够表示为这种数据结构。当插件管理程序与插件间交互的时候,双方都将各自的数据转换为这种形式,从而解决参数调用的问题,如图3.3所示。

总之,在接口间实现参数传递的方法有很多种,可以根据需要在设计的时候选用。

3.3通过规整化定义实现参数调用

 

3.6插件管理程序与插件间的通信机制

如前面章节中所说到的,在一个插件系统中,总线和各个插件独立为不同的模块,而彼此要能互相协调一致共同工作,这需要大量的插件管理程序与插件之间和插件之间的交互。在前面接口对插件的动态识别和对插件的调用过程的交互信息也是插件系统的一种通信。这种通信是一种直接的点到点应答方式,就像在计算机网络中一样,它适用于固定的两个实体之间的通信,对一个可能有许多并行的实体,并有可能产生突发的通信需求的系统来说是不够的。

在一个插件应用系统之中,为了得到更为丰富复杂的服务功能,我们往往采用并行的插件方式,在WINDOWS环境下,线程是一种很好的并行多任务手段。但是在这种方式下,前面所说的在调用和结束时候信息交互,对线程运行过程中的通信要求就无法处理。因此在插件系统中,为了系统的稳定性和安全性,需要设计和采用良好的通信机制。

 

3.7插件系统接口的管理和控制

3.7.1什么是接口的管理控制

插件系统的接口是系统中的重要部分,它是独立的插件管理程序和插件程序之间进行交互联系的纽带,协调整个系统的稳定运行。它实现智能识别插件,自动化调用插件服务,插件与插件管理程序之间的交互等功能,担负着多个插件的并行调度任务。许多简单的部件通过接口组成一个完整统一、功能齐全的灵活方便的插件系统,因此接口设计的好坏,直接影响到总线以及所有插件的设计以及整个系统的效率,并且这种影响是全局范围的从系统的设计阶段一直到它的设计测试维护和以后的修改升级。这是因为接口在系统中扮演交互桥梁的作用,插件管理程序与插件交互的方式,提供什么样的服务以及如何调用这些服务都是由接口的形式所决定。接口本身只是一些协议,通过插件管理程序中和插件中各自实现一部分而能够实现互联,那么,显而易见设计得好的接口就能够在插件管理程序和插件中较容易地实现。所以,一个插件系统的性能很大程度上依赖于接口的设计,因此就应该尽可能在设计接口的过程中最大限度地提高整个系统的性能。为了实现这个目标而用来指导我们设计接口的原则,方法,技术手段等就称为接口的管理控制。

 

3.7.2为什么需要接口的管理控制

在上一节中,我们已经简要提到接口在整个插件系统中的重要性,以及接口的性能对整个系统性能的影响。这一节中我们主要考虑接口的管理控制对整个系统的设计所带来的好处。

插件系统在设计中与传统的体系结构相比,最大的有点在于各部分独立,能够并行进行开发,又能够减少相互干扰,通过接口它们就能够互相联系起来。在这样的系统中,通信,调用,以及提供服务都在接口间透明地传递,接口就是指导插件管理程序和插件如何进行这些工作的协议和规则,插件管理程序和插件就是利用这些规则和协议在自己的内部实现对外的通信,解释外来通信数据,调用以及提供服务的机制,特别是不同开发者开发的插件产品完全依赖于这个接口来实现与系统的互联。因此在设计这些规则协议时候就应该使它们简单明了,准备充分,又能够在总线和插件中方便实现,同时不会产生歧义和错误理解,这样就可以保证系统设计开发的顺利实现。

从插件式体系结构来说,插件管理程序和插件是完全依赖于接口的,只有在相同的接口规则下,插件管理程序和插件才能够很好地连接,因此如果接口发生变化,势必使插件管理程序和相关的插件程序也要做相应的修改。因此,需要使接口尽量稳定,以保证插件系统的稳定。当然,由于一个系统的需求从设计时刻起就在不断地发生变化,这种变化发展到一定程度,必然会导致接口因为不能满足需求而进行修改。但我们可以在设计接口时就充分考虑未来会产生的需求,从而能够更加全面而完整地对接口进行设计,通过保证接口在一定时期内能够保持应用需求而保持稳定,来实现插件系统的稳定,延长它的生命周期,减少因为改变而做的工作。同样,在开发过程中,通过良好设计的接口,即使在需要进行改变时,也可以用最小的代价来实现对插件管理程序和插件的改动。由于需求的变化,接口有可能不能满足新的需求,这时就需要设计开发新的接口来满足这些新的要求。在设计新的接口的时候,我们希望它不会改变现有的接口,能够实现新的功能,又同时能够完成老的要求。减少升级换代过程中所需要的花费。

因为插件在插件系统中起着至关重要的作用,它的任何一些细小的变化和改进都会对插件系统的开发、运行和维护产生重要的影响。接口的管理控制就是为了提高插件系统的开发效率,减少接口对总线和插件的干扰,以及降低维护和升级所做的必要工作。

 

3.7.3接口控制管理的内容

接口的控制管理对插件系统有着重要的意义,这里我们根据插件系统的特点以及软件工程的原则来对接口的管理控制的内容进行讨论。

接口的不变性。从组件结构的程序来说,组件之间是通过接口来相互连接,一旦一个接口设计并开发出来以后,接口总是不变的,在需要进行改变的时候,往往是通过开发新的接口。对于插件系统而言,这样可以保证在接口不变的情况下,不同的插件管理程序可以插入同一插件来获取它的服务,而同一个插件管理程序也可以插入多个相同接口设计的插件,提高软件的可服用性。并且在今后可以继续按此接口开发新的插件,来完善和增强系统的功能。这时候如果对接口进行修改,就会造成同样服务的接口有两个不同的版本,使得已经开发出来的插件管理程序和插件在识别上出现混乱,以至出现不能相互连接的情况。特别是对于许多第三方开发的插件来说,在用户处将不能正常运行。因此必须要保证接口一旦发布便不能更改,如果由于新的需求而必须进行修改时,只能通过设计新的接口,而不能够改变已有的接口。

接口的兼容性。按照一种接口开发的插件能够插入到按同一接口开发的插件管理程序上,在一些特殊情况下,一个接口由于不能满足新的需求而设计新的接口时候,新接口的插件往往是对旧接口插件的功能的增强或补充。这种新的接口实际是旧接口的升级版本,如果按新的接口开发的插件能够提供对旧接口开发的插件管理程序的支持,或者按照新接口开发的插件管理程序也能够插入按照旧接口开发的插件,这种兼容性可以同样可以提高软件的可复用性,增长插件应用系统的生命周期。

上面所述的两个特性都是从插件系统的实际特点,而必须满足的要求。下面从软件工程的角度出发,在接口管理控制过程中还应该有:可运行性,可测试性和可维护性等内容。

可运行性。也即它的稳定性,指一个软件在一定条件下,一段时间内正确运行的稳定性。在软件工程中,需要在设计阶段开始对软件的需求分析,整体设计,模块规划等进行周密的考虑,在测试中充分模拟软件的真实运行环境进行检测,来保证软件的可运行性。从插件结构的应用程序来说,与一般的单一模块的应用程序不同,由于接口设计完成并且发布后,可以在以后开发出大量基于此接口的插件,因此就系统程序本身来说并不固定,其真实的运行环境就难以进行预测,在测试中也难以模拟出运行中的全部条件来对可靠性进行全面的检测。对此,在设计接口的过程中,一定要注意插件插入系统的复杂条件,以及可能发生的情况,防止可能出现的错误,处理好资源管理插件调用等问题。使相互间的通信,调用过程尽量清晰明白。总之一个充分设计并全面考虑了插件系统的全部细节的插件接口是整个系统可行性的基本保证。

可测试性。可测试性指对一个软件进行测试的难易程度。一个软件不管在设计阶段如何进行全面分析,编程如何细心,总会有这样或者那样意想不到的错误。但我们希望在测试阶段能够把这些问题的大部分都能够找出来并加以解决。从一般的软件来说,可以通过模块化等手段来达到,而插件系统本身就具有相当优秀的模块化结构,而且各部分都相当独立而且有清晰的划分,能够进行完全的模块独立测试,但系统联测以及考虑到未来可能会插入的新的插件就并不那么简单。主要还是要通过加强接口处的设计,使得总线和插件部分自身的错误不至于影响到其他的部分,也要防止因调试改正而引起的另外的错误。所以在接口的设计中一定要加强资源分配的控制,对会产生相互干扰的通信调用等问题也要小心谨慎,对可能会引起的重大隐患等边界条件也要妥善解决,只要能够在接口的设计过程中解决好这些问题,插件系统就拥有让人吃惊的可测试性。

可维护性。在系统设计过程中解决了可运行性、可测试性等问题,却并不等于系统就完全没有了错误,往往有一些隐藏得很深的错误和缺陷到最后也没有发现,需要到维护阶段来改正。维护阶段在软件生命周期中具有相当大的比重,而在这个阶段如果花大量的人力物力来修改,既难以承受,也会影响到后续的开发工作。

 

3.7.4接口的升级

在插件系统中,当接口不能提供对新的需求的支持或者接口有重大缺陷需要改进时,就要对原有的接口进行升级。升级的过程是在原有接口的基础上进行扩充或者修改,可能会产生的升级有扩展插件能处理的数据对象,增加调用过程的参数以增强插件的处理能力,改变通信协议等。

一般来说接口的升级都会使新开发的插件在调用等方面与旧接口的插件有所不同,依据旧接口开发的插件管理程序不能直接插入这些新的插件并调用它提供的服务。从旧接口升级而来的新接口,对以前的功能有多增强,从软件工程的角度来看,希望能够延长基于旧接口的插件管理程序的生命周期,让它能够利用这些新的和增强的功能,这就对接口的升级提出了兼容性的问题,以下是在接口的升级过程中为插件较好地实现兼容的一些建议。

尽量不该动或少改动插件服务程序的外观。因为插件式体系结构的最终目的是获得插件中提供的服务,只要服务程序都是从插件管理程序取得相同的调用参数,插件就可以方便地构造出一个基于旧接口的插件入口程序,在其中调用新的服务程序。如实在不能避免参数的数目的变动,应该使这些参数尽可能的简单,并且没有这些参数不会对服务的功能形成大的影响,以便插件能够设置默认参数调用或让用户在初始化的时候输入。插件有可能会处理各种各样的文件或数据对象,新接口中如增加了新的数据处理能力,若旧接口本身并不支持这种功能,兼容就不容易实现。应该在一开始设计系统时候,将数据的输入输出、管理也设计为一种插件形式,插件管理程序仅负责初始化系统以及管理插件的工作。遇到增加处理新的数据对象能力时,就可以开发一个处理这种数据对象的插件,对旧的插件管理程序做少许修改或者不做修改就能够实现插件的兼容。如对通信协议进行了修改,应使得这种修改大部分局限于插件和插件管理程序的内部,而不影响它的外观,并努力通过对旧接口的插件管理程序进行适当的修改以支持这种新的接口。总体来说,如图3.4的策略可用于简化接口的兼容。策略一,将旧接口的调用转化为新接口的方法;策略二,在旧接口的基础上实现新接口。

3.4接口的兼容实现

事实上,一条根本的规则在于:接口修改后,应尽量使插件管理程序和插件对外的部分保持不变,如发生变化,也必须能通过对插件管理程序做少量的修改升级就可以实现兼容,以减少在维护阶段所需要做的工作。因此,新的接口设计后,我们只是希望在新插件尚未大量开发出来的时间内能够充分利用以前的开发,而不是不切实际地永久地延长它们的生命周期。

在本章分析了插件式应用程序的结构,对它的技术基础和特点进行了介绍。详细讨论了插件系统的工作原理、过程以及其中的细节,对插件系统中的最重要的接口部分做了全面的研究和探讨。同时根据在对插件系统开发和研究中的工作实践中的经验,对插件系统设计中的一些问题提供了解决办法,原则和建议。在下章中将研究具体的插件系统的技术和实现。

 


4.基于.NET的插件体系结构的实现

4.1插件树体系结构的实例

当前,软件开发人员一直在寻求有效的软件体系结构和更好的软件开发方法,其中基于插件体系结构来开发软件是软件开发人员的一个尝试。使用此种方式开发的系统往往更利于扩充功能,使开发更有效率。很多成熟成功的软件包括很多系统软件和应用软件在分析设计开发阶段都采用了此种方式。

例如PhotoShopWinamp,他们都有“插件”的概念,允许其他开发人员根据系统预定的接口编写扩展功能。所谓的插件就是系统的扩展功能模块,这个模块是以一个独立文件的形式出现的,与系统是相对独立。在系统设计期间并不知道插件的具体功能,仅仅是在系统中为插件留下预定的接口,系统启动的时候根据插件的配置寻找插件,根据预定的接口把插件挂接到系统中。

这样的方式带来什么样的优点呢?首先是系统的扩展性大大的增强了,如果我们在系统发布后需要对系统进行扩充,不必重新编译,只需要修改插件就可以了。其次有利与团队开发,各个功能模块由于是以插件的形式表现在系统中,系统的每日构造就很简单了,不会因为某个模块的错误而导致整个系统的构造失败。失败的仅仅是一个插件而已。

 PhotoShopWinamp的插件系统是比较简单的,他们首先实现了一个基本的系统,然后在这个系统的基础上挂接其他扩展的功能插件。这是比较简单的插件想法,能够扩展的功能相当有限。当前更流行的被认为更加有效的插件体系结构不是这样的,当前流行的做法往往把主程序本身也做成插件,也就是说插件分为宿主插件和扩展插件两类,这样的话才可能有好的扩展性。象Eclipse的扩展和扩展点的思想,和SharpDevelop的插件树的思想比较好解决了扩展性的问题。

SharpDevelop的插件系统更加强大,它的整个系统的基础就仅仅是一个插件管理系统,而用户看到的所有的界面、功能统统都是以插件的形式挂入的。在这样的一个插件系统下,我们可以不修改基本系统,仅仅使用插件就构造出各种各样不同的系统。

SharpDevelop是一个在微软.NET环境下采用.NET当中包含的新的编程语言C#开发的成功的开源的集成开发环境。本文的后面章节,将以这个IDE所采用的插件树体系结构为蓝本,深入分析它的设计理念和具体实现细节,研究它插件管理系统以及插件的接口和其间用到的技术。

 

4.2插件树   

4.2.1插件树的概念

插件树是一个树状数据结构。SharpDevelop的每一个实例只有一个插件树,所以它是通过Singleton设计模式来实现的。

4.1给出了插件的基本思想,插件插入到树中,树包含了整个系统。在SharpDevelop中,物理地,XML文件和XML文件中引用的DLL集定义了一个插件。DLL提供了实现代码,XML定义了将其插入到插件树的方式和位置。

 

4.1插件树逻辑图

如图4-1中所示,插件树中包含两种不同的对象:节点和路径。插件是插件树的节点,路径的作用是用来构造树,因此不是真正的节点。IDE根据这些节点定义的内容确定行为,可以说,节点是包含行为定义的路径。

SharpDevelop中使用插件树的任何一部分都必须了解获取它所需节点的路径。例如,SharpDevelopOptions对话框需要从/SharpDevelop/Dialogs/OptionsDialog路径来获取信息。在该路径及其子路径中,只存储对话框面板,对话框从子树中获取所有面板,将它们插入到显示的树视图中。换言之,定义了特定路径的插件也需要定义可以插入到其下的节点的接口。如果Options对话框要添加新的面板,插件必须将正确节点插入到路径,对话框将显示它们。在SharpDevelop中,所有可见元素都由节点定义;大多数不可见元素在插件树中也作为节点来实现。

 

4.2.2插件树的优点

与其它涉设计方法相比,插件树有很多优点:

1)可以通过其他插件扩展已有插件。但这样做面临的主要问题是:其他插件执行行为的时候必须通知已有的插件。这是必需的,因为要扩展另一个插件,就必须扩展另一个插件的“行为”,它们之间必须保持某种类型的通信。这是插件结构需要解决的主要问题。插件树结构的这个优点为软件的设计和开发提供了级别非常高的灵活性。

2)包含可执行代码的程序集(Assembly)文件不必保存在同一个目录中。开发人员可以将它们存储在任何需要和方便的位置,插件管理系统都能够将插件文件加载。这就使得插件的添加、删除和部署都非常容易,只需要将包含XML定义和所需程序集的插件文件夹复制到SharpDevelopAddIns文件夹就可以了。

3)使用插件树结构,插件不必实现自己的插件结构,因为所有插件都基于一个系统——插件树。往系统中添加插件,只需要通过插件树这个接口,就可以定制任何的功能。

 

4.2.3插件树的上层结构

插件树是由一个提供给其余应用程序所用功能的接口定义的。首先,AddIn树包含ConditionFactoryCodonFactory对象,这些工厂创建了插件树节点的内容。AddIns属性用于得到所有载入插件的集合。GetTreeNode方法非常重要,是使用插件树的插件唯一所需要调用的方法,由这个方法可以通过XML中的定义和描述生成实际运行的对象。其余的方法和属性当前只在核心程序集内部使用。InsertAddInRemoveAddIn方法可以用于在SharpDevelop中实现插件的管理。下面是插件树的接口代码:

  /// This is the basic interface to add-intree.

  public interface IAddInTree

  {

    /// Returns the default condition factory.ICondition objects

    /// are created only with this factoryduring the tree

    /// construction process.

    ConditionFactory ConditionFactory {

      get;

    }

 

    /// Returns the default codon factory. ICodonobjects

    /// are created only with this factoryduring the tree

    /// construction process.

    CodonFactory CodonFactory {

      get;

    }

 

    /// Returns a collection of all loadedadd-ins.

    AddInCollection AddIns {

      get;

    }

   

    ///Returns a TreeNode corresponding to <code>path</code>.

    IAddInTreeNode GetTreeNode(string path);

   

    /// Inserts an AddIn into the AddInTree.

    void InsertAddIn(AddIn addIn);

 

    /// Removes an AddIn from the AddInTree.

    void RemoveAddIn(AddIn addIn);

   

    /// This method does load all codons andconditions in the given assembly.

    /// It will create builders for them whichcould be used by the factories

    /// to create the codon and conditionobjects.

    Assembly LoadAssembly(string assemblyFile);

  }

 

4.3代码子

代码子(Codon)是在SharpDevelop的插件树中的一个专有名词,代码子的功能是用来描述一个功能模块。每一个功能模块内部都对应一个实现了具体功能的ICommand接口的实现。在SharpDevelop的插件树概念中,插件是可以包含多个功能模块的集合。为了方便访问各个插件中的功能模块,代码子给各种功能定义了基本属性,分别是功能模块的标识(ID),功能模块的类型(Name)等。我们可以根据需要定义自己需要类型的代码子。

为了避免其他术语导致语义和理解上的错误,SharpDevelop的设计者选择代码子(Codon)这个没有被计算机界广泛使用的术语。代码子类的对象存储于树结构中,在运行时用于表示代码子XML节点。在SharpDevelop中,代码子是ICodon接口的一个实现。

  /// The ICodon interface describes the basicfuncionality

  /// a codon must have.

  public interface ICodon

  {

    /// returns the add-in in which this codonobject was declared

    AddIn AddIn {

      get;

      set;

    }

   

    /// returns the name of the xml node ofthis codon. (it is the same

    /// for each codon type)

    string Name {

      get;

    }

 

    /// returns the ID of this codon object.

    string ID {

      get;

    }

   

    /// returns the Class which is used in theaction corresponding to

    /// this codon (may return null, if noaction for this codon is

    /// given)

    string Class {

      get;

    }

    

    /// Insert this codon after the codonsdefined in this string array

    string[] InsertAfter {

      get;

      set;

    }

   

    /// Insert this codon before the codonsdefined in this string array

    string[] InsertBefore {

      get;

    }

   

    /// Creates an item with the specified subitems and the current

    /// Condition status for this item.

    object BuildItem(object owner, ArrayListsubItems, ConditionFailedAction action);

  }

 

4.4条件

条件用于插件树,用来表示节点是否是活动的,用来决定是否创建它。这可以用于动态更改插件树,例如,当菜单项不用或者禁用的时候,可以通过条件使菜单项不可见,这些都可以通过条件来完成。下面是在XML说明文件中一个假想的条件的例子:

<Conditionopenproject=”*” action=”Disable”>

<!—Here may followa menu definition -->

</Condition>

<Condition>节点包含一个action属性,被设置为Disable。当前,条件的可能选项有三种:NothingExcludeDisable。只有条件为false时,才会发生这些动作。

若条件失败,则Nothing动作表示什么也不做,选择该项等同于未使用条件。完整性情况例外。如未指定动作属性,则Exclude为默认动作,在代码子对象调用BuildItem方法的时候,它实际上只是从插件树中删除项目。Disable试图禁用项目,代码子对象必需自己处理禁用情况,只有对象了解它可否禁用,以及如何禁用。例如,若禁用菜单项,必须将菜单项的Enabled属性设置为false

从代码上看,条件和代码子类似,只是条件不是由名称来区分,而是用不同属性进行标识,即两个不同条件不能提供同一个所需属性集,因为实现只选择一个条件。因为条件和代码子的区别,所以它们的接口有所不同,条件不需要ID或者排列信息。

  /// Default actions, when a condition isfailed.

  public enum ConditionFailedAction {

    Nothing,

    Exclude,

    Disable

  }

 

  /// The ICondition interface describes thebasic funcionalaty

  /// a condition must have.

  public interface ICondition

  {

    /// Returns the action which occurs, whenthis condition fails.

    ConditionFailedAction Action {

      get;

      set;

    }

   

    /// Returns true, when the condition isvalid otherwise false.

    bool IsValid(object caller);

  }

 

4.5插件管理

下面就是一个插件定义的XML文件:

<AddInname 
= "Styleguide Checker"

      
author      = "Mike Krueger"

      
copyright   = "GPL"

      
url  = "unknown"

      
description = "Checks theSharpDevelop C# Style Guidelines for combines"

      
version     = "1.0.0">

 

 
<Runtime>

   
<Importassembly="StyleguideChecker.dll" />

 
</Runtime>

 

 
<Extension path ="/SharpDevelop/Workbench/MainMenu/Tools">

   
<MenuItem id = "StyleguideChecker"

         
insertafter = "Separator1"insertbefore = "Options"

         
label       = "Check Styleguide inCombine"

         
description = "Checks theSharpDevelop C# Style Guidelines for combines"

         
class       ="ICSharpCode.StyleguideChecker.StyleGuideChecker"

   
/>

 
</Extension>

</AddIn>

在上面的插件定义文件中,我们可以看到在根节点中包含插件的属性,可以用它们来携带插件信息。<Runtime>节点包含该插件所需程序集的相关信息,它通常是一个程序集列表,包含用于定义文件的类。之后定义了一个<Extension>节点,它包含一个Path属性和一个子节点。在本例子中,子节点是包含一些属性的菜单项,这个子节点被放置在插件树中的/SharpDevelop/Workbench/MainMenu/Tools路径下面,我们将这个子节点就称之为代码子。换言之,同<Runtime><Extension>节点不同,插件树没有定义<MenuItem>节点类型,它是通过代码子机制定义的。

插件管理由AddIn类来完成。该类是XML插件文件由代码表示的形式,用于完成读取插件配置文件初始化插件等任务。图4.2简单列出了插件类:

4.2插件类

SharpDevelop中的插件和代码子的概念是不同的。插件通过插件配置文件来配置,在代码中用AddIn类来表示,插件中可能包含多个代码子,SharpDevelop把具体的功能模块封装为代码子,用Codon来封装,而使用Command类来包装具体的功能。在SharpDevelop的整个基础插件系统部分没有任何的GUI操作,实现了很好的解耦效果。

SharpDevelop的这种设计,增加了它的插件系统的灵活性。在插件配置文件中的Extension节点下的Codon节点名称并没有在代码中和具体的Codon类联系起来,而是通过CodonNameAttributeCodon联系起来。这样做的好处是,SharpDevelopCodonXML标签一样具有无限的扩展能力。

 

4.6属性管理

属性管理是指管理,保存和加载所有用户使用程序的时候更改的“选项”的能力。这些选项被称之为属性。例如,用户可能自己设定了界面的显示语言或者显示风格等,这些都需要通过某种通用的方式保存下来,并且需要的时候得到。属性是一个通用概念,就是一个关键字和一个值。下图列出了SharpDevelop的属性管理系统。

4.3属性管理系统

IXmlConvertable接口是IProperties接口的基础。属性管理系统可以将简单类型存储到属性。IXmlConvertable接口的得作用就是将对象的内容转换为XML,随后在需要的时候恢复原来的状态。下面是这个接口的代码:

  public interface IXmlConvertable

  {

    /// Converts a XmlElement to anIXmlConvertable

    object FromXmlElement(XmlElement element);

   

    /// Converts the IXmlConvertable object toa XmlElement

    XmlElement ToXmlElement(XmlDocument doc);

  }

这个接口的定义非常简单,只有两个方法。FromXmlElement方法有一个XmlElement参数,并返回一个用元素内容初始化的新实例。因为该对象可能发生变化,而文件格式保持不变,所以没有用.NET的串行化来实现,而且采用.NET的串行化来实现的话,所需花费的开销也会增加。

IProperties接口继承自IXmlConvertable接口,应用程序的其他部分与这个简单接口共同工作,完成属性的保存和获得。IProperties接口包含获取和设置属性的函数,还定义了复制IProperties对象的方法。下面是IProperties接口的代码:

  /// The IProperties interface defines a setof properties

  public interface IProperties :IXmlConvertable

  {

    /// Gets a property out of the collection.The defaultvalue must either

    ///have a cast to a string (and back) or implement the

    /// IXmlConcertable interface.

    object GetProperty(string key, objectdefaultvalue);

   

    /// Gets a property out of the collection.

    object GetProperty(string key);

   

    /// Gets a int property out of thecollection.

    int GetProperty(string key, intdefaultvalue);

   

    /// Gets a bool property out of thecollection.

    bool GetProperty(string key, booldefaultvalue);

 

    /// short property out of the collection.

    short GetProperty(string key, shortdefaultvalue);

 

    /// Gets a byte property out of thecollection.

    byte GetProperty(string key, bytedefaultvalue);

 

    /// Gets a string property out of thecollection.

    string GetProperty(string key, stringdefaultvalue);

   

    /// Gets a enum property out of thecollection.

    System.Enum GetProperty(string key,System.Enum defaultvalue);

   

    /// Sets the property key to the value.

    void SetProperty(string key, object val);

   

    /// Returns a new instance of IPropertieswhich has

    /// the same properties.

    IProperties Clone();

   

    /// The property changed event handler, itis called

    /// when a property has changed.

    event PropertyEventHandler PropertyChanged;

  }

 

4.7用插件创建应用程序

在本章前面的章节描述了插件管理系统的构建和实现的一些细节,这一节着重介绍如何应用插件来构造和创建应用程序。

 

4.7.1使用代码子

SharpDevelop中代码子用来封装具体的功能模块,其中包含调用功能模块所需服务的必要信息和方法。在插件定义模块中,代码子与<Extension>标签模块的内容相对应,例如下面的定义:

<Extension path ="/SharpDevelop/Workbench/Ambiences">

   <Class id=".NET" class ="ICSharpCode.SharpDevelop.Services.NetAmbience"/>

</Extension>

<Extension>节点内部定义了一个代码子,<Class>表示该代码子是一个 Class类型,接着定义了该代码子的ID和具体实现该代码子的类名ICSharpCode.SharpDevelop.Services.NetAmbience。运行期间将通过反射来找到对应的类并创建出来,这一点也是我们无法在以前的语言中实现的。

上面的插件配置文件片断介绍了如何通过代码子来扩展功能,下面看一下如何在XML插件配置文件中定义子项目。下面来看子项目定义:

<Extension path ="/ExampleMenu">

  <MenuItem id = "AddMenu" label ="Add">

    <MenuItem id = "AddItem" label= "Add item" class ="MyAddItem">

    <MenuItem id = "AddFolder"label = "Add folder" class ="MyAddFolder">

  </MenuItem>

</Extension>

上面的代码片断不是取自SharpDevelop的实际插件文件,只是为了做示范。上面的代码片断是简写形式,完整形式为:

<Extension path ="/ExampleMenu">

  <MenuItem id = "AddMenu" label ="Add"/>

</Extension>

 

<Extension path ="/ExampleMenu/AddMenu">

  <MenuItem id = "AddItem" label ="Add item" class ="MyAddItem">

  <MenuItem id = "AddFolder" label= "Add folder" class ="MyAddFolder">

</Extension>

SharpDevelop的插件文件的第一个版本是最常用版本,它使得XML文件更具有维护性。每个代码子都是插件树中的路径,ID是路径名,所以同一路径中的两个代码子不应该具有同一个ID

如前文所述,代码子描述了一个功能模块,而每一个功能模块都是一个ICommand接口的实现,根据代码子的不同,对应了不同的Command。下面是ICommand接口的代码:

  /// A basic command interface. A command hassimply an owner which "runs" the command

  /// and a Run method which invokes thecommand.

  public interface ICommand

  {

    /// Returns the owner of the command.

    object Owner {

      get;

      set;

    }

   

    /// Invokes the command.

    void Run();

  }

该接口可以用作菜单项、按钮等的基接口。菜单项仅仅是命令,它实现Run方法。Owner属性用于回调。

 

4.7.2通过条件接合插件

插件树的定义是静态的,无法在运行时刻动态更改。理论上,可以通过删除插件和树节点路径的方式来删除插件,但是如果要实现启用或者禁用某个模块的功能,这种做法不方便。本节介绍如何通过条件来结合插件。

下面是插件定义文件中的标准条件结构:

<Extensionpath = "[ExtensionPath]">

  <Conditional [Attributes][action="Disable"]>

    ...(Codons that have the condition applied)

  </Conditional>

</Extension>

通常可能需要多个条件组合,以形成包含多个选项的条件。例如,如果需要条件A和条件B都为真的情况下代码子模块才可用,则可以采用如下的方式:

<Extensionpath = "[ExtensionPath]">

  <ConditionalA>

    <ConditionalB>

      ... (Codons that have the condition A andB applied)

    </ConditionalB>

  </ConditionalA>

</Extension>

如果只有这两种情况,则以上的解决办法就够用了。但是问题在于我们有时候需要条件A为真,条件B为假的情况下代码子才可用,这就需要创建更为复杂的条件系统。所以在SharpDevelop插件定义中增加了“与”、“或”和“非”等XML节点,用于表示复杂的条件,主Conditional
XML节点包含应用接合条件时候将要采取的动作,如下图所示:

<Extension path ="[ExtensionPath]">

  <Conditional[action="Disable"]>

    <And>

      <Condition [Attributes]/>

      <Condition [Attributes]/>

      <Condition [Attributes]/>

      <Or>

        <Condition [Attributes]/>

        <Condition [Attributes]/>

      </Or>

      <Not>

        <Condition [Attributes]/>

      </Not>

    </And>

    ... (Codons that have the joined conditionapplied)

  </Conditional>

</Extension>

条件的定义类似于代码子,不同点是条件不通过名称来互相区分,而是根据所需要的属性来互相区别。可以将条件添加到插件树的条件生成器,其方式类似于代码子。一个插件中定义的所有运行时库将被自动搜索,以查找已经定义的条件,然后将这些条件添加到条件生成器,以供该插件或者其他插件使用。

通过条件就可以实现在特定情况下禁用代码子。例如,在SharpDevelop窗口菜单中有一些MDI窗口专用的菜单项,只有将当前布局管理器设置为MDI布局,且至少有一个窗口处于活动状态时候,才启用它们,这个需求可以通过条件来实现如下:

<Conditionalaction="Disable">

  <And>

    <Condition activewindow="*"/>

    <Conditionstring="${property:SharpDevelop.Workbench.WorkbenchLayout}"

    equals="MDI"/>

  </And>

  ...

</Conditional>

代码子和条件的主要区别就在于:条件不创建项目,而是用IsValid方法来确定条件是否成功。在SharpDevelop中定义好了很多条件,如上面的活动窗口条件,该条件检查窗口是否处于活动状态。

本章开始详细介绍了在SharpDevelop中插件管理程序的结构和一些重要概念及其实现细节。通过对SharpDevelop插件树、代码子和条件等的介绍,使我们大体上了解了插件树式插件管理程序的组成部分和工作原理。本章最后又介绍了如何利用代码子和条件在插件管理程序的基础上构建应用程序。通过这章我们对通过插件树体系结构和开发方法构建应用程序有了总体上的认识,下一章将介绍一个具体的例子,利用已经介绍的技术,实现在SharpDevelop的插件树的插件管理程序上添加一个插件,扩充原有的功能。

 


5.通过插件扩充功能

5.1插件简介

前面章节的介绍了插件式体系结构和开发方法的优点和为开发所带来的好处,及其研究它的重要意义与价值所在。分析了插件式应用程序的结构,对它的技术基础和特点进行了介绍,详细讨论了插件系统的工作原理、过程以及其中的细节。接着分析了开源的集成开发环境SharpDevelop的插件体系结构和实现细节,使我们对完全采用插件结构开发的SharpDevelop的插件管理程序框架有了大体上全面的认识。本章在前面分析总结的基础上,利用和依照SharpDevelop插件体系结构提供的接口开发一个简单的插件,目的是加深对插件式体系结构和开发方法的了解,体会采用这种结构和方法扩充功能的方便和强大。

这个插件名字叫做HelloWorld,就是在SharpDevelopTools菜单下面增加一个名字为HelloWorld的菜单项,在单击此项后,在SharpDevelop的主视图中会增加标题为HelloWorld的一页,上面显示的内容为HelloWorld。当然这个插件本身的功能很简单,但我们的目的是了解如何利用SharpDevelop的插件体系结构框架和接口来扩充功能和组织应用,通过这个简单的插件,可以帮助我们了解整个系统的架构,使以后扩充更为复杂的功能也不是难事。通过研究别人的优秀的程序的体系结构和开发方法,揣摩别人的程序组织技巧和思路,使我们在设计规划自己的程序的的时候得到启迪并有所借鉴。

5.1显示了插件安装后主界面菜单的变化。图5.2是插件安装后执行菜单命令后的运行效果图。

5.1安装插件后主界面变化图

5.2HelloWorld插件运行效果图

 

5.2插件的配置

插件的配置就是描述该插件并指定如何把插件挂到系统中。按照SharpDevelop的插件树思想,每一个插件在系统中都有一个扩展点的路径。编写一个插件首先要根据插件接口编写功能模块实现一个Command类,然后指定Command类的扩展点路径,把插件挂接到插件树中。下面是HelloWorld插件的配置文件Hello.addin

<AddInname       
= "HelloWorld"

      
author      = "Xu Hongxing"

      
copyright   = "GPL"

      
url         ="http://www.cnblogs.com/Xuhx"

 
     description = "A Hello World AddinExample"

      
version     = "1.0.0">

 

 <Runtime>

 
<Importassembly="Hello.dll"/>

 </Runtime>

 

 <Extension path ="/SharpDevelop/Workbench/MainMenu/Tools">

 
<MenuItem id = "Hello World"

  
label = "HelloWorld"

  
class= "Addins.Hello.HelloCommand"/>

 </Extension>

</AddIn>

在插件配置文件中,Runtime节点指定了插件功能模块所在的库文件Hello.dll的具体路径,在Extension节点中指定了扩展点路径/SharpDevelop/Workbench/MainMenu/Tools,然后在Extension内指定了它的CodonMenuItem以及具体的ID、标签、Command类名。这样插件出现在了Tools菜单下。

如果对于每一个插件都编写这样的配置文件,那么插件的库文件和插件配置文件是一一对应了。不过这样就也会带来小小问题,在这样的一个以插件为基础的系统中,每个菜单、工具栏、窗体面板都是一个插件,那么我们需要为每一个插件编写配置文件,这样就会有很多的配置文件。插件太多了可能不太好管理。SharpDevelop也想到了这个问题,于是它允许我们把多个插件的配置合并在一个插件配置文件中。

 

5.3插件的实现代码

下面是HelloCommand.cs代码单元。如前所述,代码子描述了一个功能模块,而每一个功能模块都是一个ICommand接口的实现,根据代码子的不同,对应了不同的CommandHelloWorld插件是对SharpDevelopTools菜单进行了扩展,所以对应MenuItem代码子,所以HelloCommand继承自AbstractMenuCommand类。MenuItem代码子是SharpDevelop中用来描述菜单项功能模块的代码子,如果需要,用户也可以定义自己的代码子,这里不需要,因为SharpDevelop已经提供了封装菜单项的代码子。而AbstractMenuCommand类是SharpDevelop中需要完成具体功能的菜单项Command需要实现的抽象类。当用户在SharpDevelop界面中选择HelloWorld菜单项的时候,将会执行HelloCommand类中的Run方法。

usingSystem;

usingSystem.Windows.Forms;

usingSystem.CodeDom.Compiler;

 

usingICSharpCode.SharpDevelop.Gui;

usingICSharpCode.SharpDevelop.Gui.Pads;

usingICSharpCode.Core.AddIns;

usingICSharpCode.Core.AddIns.Codons;

usingICSharpCode.SharpDevelop.Services;

 

namespaceAddins.Hello

{

 
public class HelloCommand:AbstractMenuCommand

 
{ 

   
public override void Run()

   
{ 

     
using (HelloContent viewContent = newHelloContent() )

     
{

       WorkbenchSingleton.Workbench.ShowView(viewContent);

     
}

   
}   

 
}

 

 
public class HelloContent:AbstractViewContent

 
{

   
HelloControl viewControl = newHelloControl();

 

   
public override Control Control

   
{

    
 get

     
{

       
return viewControl;

     
}

   
}

 

   
public override bool IsDirty

   
{

     
get

     
{

       
return false;

     
}

     
set

     
{

     
}

   
}

 

   
IWorkbenchWindow workbenchWindow;

   
public override IWorkbenchWindow WorkbenchWindow

   
{

     
get

     
{

       
return workbenchWindow;

     
}

     
set

     
{

       
workbenchWindow = value;

       
workbenchWindow.Title = "HelloWorld";

     
}

   
}

 

   
public HelloContent()

   
{

     
UntitledName = "Hello World";

   
}

   

 

   
public override bool IsViewOnly

   
{

     
get

     
{

       
return true;

     
}

   
}

   

   
public void Undo(){}

   
public void Redo(){}

   
public override void SaveFile(){}

   
public override void SaveFile(string filename){}

   
public override void LoadFile(stringfilename){}

 
}

}

在上面的代码中,HelloContent类继承自AbstractViewContent类。AbstractViewContent类是SharpDevelop中所有分页视图的基接口,可以在它的基础上进行扩展用于完成显示信息或者编辑数据功能。下面是HelloControl.cs代码单元。HelloControl类继承自UserControl类,完成在SharpDevelop的视图中显示“HelloWorld”信息,UserControl类是.NET的一个可以实现用户自定义控件的基类。

usingSystem;

usingSystem.Collections;

usingSystem.ComponentModel;

usingSystem.Drawing;

usingSystem.Data;

usingSystem.Windows.Forms;

 

usingICSharpCode.SharpDevelop.Gui;

usingICSharpCode.Core.AddIns;

usingICSharpCode.Core.AddIns.Codons;

 

namespaceAddins.Hello

{

 

 
public class HelloControl :System.Windows.Forms.UserControl

 
{

   
private System.Windows.Forms.Label lblInfo;

 
private System.ComponentModel.Containercomponents = null;

 

 
public HelloControl()

 
{

   
InitializeComponent();

 
}

 

 
protected override void Dispose( booldisposing )

 
{

   
if( disposing )

   
{

   
if(components != null)

   
{

     
components.Dispose();

   
}

   
}

   
base.Dispose( disposing );

 
}

 

 

 
private void InitializeComponent() {

     
this.lblInfo = newSystem.Windows.Forms.Label();

     
this.SuspendLayout();

     
//

     
// lblInfo

     
//

     
this.lblInfo.Dock =System.Windows.Forms.DockStyle.Fill;

     
this.lblInfo.Font = newSystem.Drawing.Font("Tahoma", 43F, System.Drawing.FontStyle.Regular,System.Drawing.GraphicsUnit.World, ((System.Byte)(134)));

     
this.lblInfo.Location = newSystem.Drawing.Point(0, 0);

     
this.lblInfo.Name = "lblInfo";

     
this.lblInfo.Size = newSystem.Drawing.Size(150, 150);

     
this.lblInfo.TabIndex = 0;

     
this.lblInfo.Text = "HelloWorld!";

     
//

     
// HelloControl

     
//

     
this.Controls.Add(this.lblInfo);

     
this.Name = "HelloControl";

     
this.ResumeLayout(false);

   
}

 
}

}

HelloWorld插件的代码可以看出,插件需要了解它所要扩展的扩展点提供的接口信息。HelloCommand类需要继承自AbstractMenuCommand类,在SharpDevelop分页视图中显示信息需要用到AbstractViewContent类等,这些都印证了这一点。

本章介绍了在SharpDevelop中定义新的插件扩展原有功能的方法,描述了一个插件从实现到配置的所有步骤的细节,用到了以前分析的结果,印证了以前总结的理论,对加深理解插件体系结构和开发方法的概念与优点有重要意义。

 


6.结论

本文针对当前计算机技术不断创新,对软件需求越来越广,软件的功能越来越复杂,开发周期越来越长,开发和维护难度都逐渐增大的现状,对软件插件体系结构和开发方法进行了深入研究,并对采用插件树式插件体系结构和开发方法开发的比较成功的集成开发环境SharpDevelop的结构和一些技术细节进行了分析和实践。本文主要做了下面一些工作:

.针对插件式体系结构和开发方法的显著优点,本文对插件式体系结构的工作原理进行了深入的分析和研究,结合对SharpDevelop的分析,探索了现在插件式结构的研究方向和应用。

.通过研究和实践,对插件式体系结构的接口,管理和插件等进行了详细的论述,对插件调用问题、插件识别问题和利用插件组织具体应用等问题进行了深入研究,提出了一些原则和建议。

.本文综合了面向对象设计开发方法、微软的.NET技术、XML技术和设计模式等技术,对插件式体系结构和开发方法实现中的一些具体问题提供了技术解决手段。

由于时间原因和自己水平所限,对于插件式体系结构和开发方法还有很多地方需要研究,文中也难免有不足之处,请各位专家和老师不吝赐教。

 


 

参考文献

[1] 英宇,林琪,费广正.Microsoft.NET XML高级编程[M].北京:清华大学出版社,2002

[2] 李建忠.Microsoft.NET框架程序设计[M].北京:清华大学出版社,2003

[3] 陈月波著.电子商务网站建设[M] .浙江:浙江大学出版社,2003

[4] 谢冬青,冷健著.PKI原理与技术[M].北京:清华大学出版社,2004

[5] 杨秀章译


.COM技术内幕


[M]. 北京:清华大学出版社,


1999


[6] 


王建华译


.Windows核心编程


[M].北京:机械工业出版社,


2000


[7] 潘爱民,新语译.深入解析ATL[M].北京:中国电力出版社,2001

[8] 朱三元等著.软件工程技术概论[M].北京:科学出版社,2002

[9] 潘锦平著.软件系统开发技术[M].西安:西安电子科技大学出版社,2002

[10]薛兴涛著.C#软件项目开发全程剖析[M].北京:清华大学出版社,2003

[11] 邵维忠等译.面向对象的设计[M].北京:北京大学出版社,1994

[12] 黄迪明著.软件技术基础[M].成都:电子科技大学出版社,1998

[13] 张晓坤译..NET本质论[M].
北京:中国电力出版社,2004

[14] 杨浩等译.C#高级编程[M].北京:清华大学出版社,2002

[15] 李英军等.设计模式[M].北京:机械工业出版社,2002

[16] Alan Shalloway等著.设计模式解析[M].北京:中国电力出版社,2003

[17] 基于异步插件协议的MIME过滤器的原理与实现[J] .计算机科学与实践,200412

[18] William Ford,William Topp. Data Structures with C++[M].北京:清华大学出版社,2003

[19] Francesco Balena.Programming Microsoft Visual Basic.NET, Microsoft Press.
北京:清华大学出版社,2003

[20] Christian Holm, Mike Kruger, Bernhard Spuida.
Dissecting a C# Application Inside SharpDevelop.
http://www.icsharpcode.net, 2005.4

[21] Charles Petzold.Programming Microsoft Windows with Microsoft Visual Basic.NET.武汉:华中科技大学出版社,2004

[22] Eric Bruchez, OmarTazi.
Integrating JSP/JSF and XML/XSLT. http://www.theserverside.com2005.3

[23] 邱仲潘等译.UMLRational Rose 2002
从入门到精通[M].北京:电子工业出版社,2002

[24] XML中国论坛.XML实用进阶教程
[M].北京:清华大学出版社,2000

[25] 侯捷著.STL源码剖析[M].武汉:华中科技大学,2003

[26] Andrew Hunt, David Thomas.The Pragmatic Programmer [M].北京:中国电力出版社,2003

[27] 龚晓庆,卞雷等译.面向对象设计UML实践[M].北京:清华大学出版社,2005

[28] 冉晓旻,罗邓,郭炎译.Visual C#.NET技术内幕[M].北京:清华大学出版社,2004


 

 

作者发表

 

1、基于异步插件协议的MIME过滤器的原理与实现,计算机科学与实践,200412

 


 

 

独创性声明

 

本人声明所呈交的学位论文是本人在导师指导下进行的研究工作及取得的研究成果。据我所知,除了文中特别加以标注和致谢的地方外,论文中不包含其他人已经发表或撰写过的研究成果,也不包含为获得XX大学或其他教育机构的学位或证书而使用过的材料。与我一同工作的同志对本研究所做的任何贡献均已在论文中作了明确的说明并表示谢意。

本学位论文成果是本人在XX大学读书期间在导师指导下取得的,论文成果归XX大学所有,特此声明。

 

学生签名_____________
指导教师签名___________

 

   
   

 


 

 

致谢

 

在本文完成之际,特向导师XX副教授致以最衷心的谢意。在研究生学习的过程中,导师给了我很多帮助和指导。

最后感谢在我论文完成过程中,研究生学习过程中和人生旅途中给我帮助和关心的亲人、朋友、老师和同学。

抱歉!评论已关闭.