陈海燕 朱宇来 林阳 马蕾 常莹
摘 要:在计算机应用程序的开发过程中,软件结构的前期设计对代码实现和后期维护、扩展和升级等工作的影响重大,直接影响软件的代码编写规模、可扩展性等。良好的结构设计能够缩小软件规模,提高代码的复用率。其中,总线式结构设计是维持软件可扩展性的一种方法。将软件按功能设计为不同的模块(插件),将这些模块组合在一起,通过总线管理各个模块协同工作。当软件需要扩展时,加入新模块或更新旧模块就可以实现。该文讲述的是基于总线式的结构设计方法,利用.NET框架中的反射(Reflection)机制对类型信息的描述能力,提出一种模块组合结构的优化方法,用来增强软件的可维护性,并保持对用户自定义数据结构的处理能力。
关键词:软件开发 结构设计 模块 总线 插件 优化 C# NET 反射
中图分类号:TH703 文献标识码:A 文章编号:1672-3791(2014)10(a)-0021-02
计算机技术发展至今,程序结构设计思想已经逐步成熟。面向对象程序设计、模块化设计等概念已经深入人心。应用程序的可维护性已经成为衡量软件质量的重要标准。增强程序的可维护性可通过良好的结构设计、模块化的功能区分等方法实现。结构设计越清晰,模块间的耦合度越低、相关程度越小,程序的可拆分、可组件、可扩展的能力越强。总线—插件式的程序结构设计是提高软件质量的重要方法。总线负责各个模块的加载、事务的调度、资源的分配和消息的管理等。模块(插件)是一种独立的功能单元,完成总线安排的事务处理、数据转换工作。这样的方法避免了功能修改、扩展时,局部功能的改动需要修改大量与之相关的代码,只需要改动模块这个功能单元就可以实现了。这种程序设计方法就好比是PC机的硬件结构,主板是所有硬件的平台,通过主板总线完成各硬件间的消息、数据传递,进行功能组合。若要升级计算机的性能,功能等,只需要升级个别部件,或插入特定功能的扩展硬件就可以了。
1 模块独立性
模块的独立性越强,其灵活程度越低。也就是说为了保证模块的独立性,必须不与其他模块或类型产生依赖关系,函数的参数不能使用其他模块中的定义类型。所以,使用通用性强的变量类型作为模块的出入口参数能够保证其较高的独立性。但在以处理复杂数据结构为核心功能的大型应用程序(如地理信息系统、图形处理系统等)中,每一个功能模块都围绕着复杂数据类型(空间数据、图形数据等)进行设计。若将模块中处理复杂数据类型的函数参数设计为自定义的复杂数据类型,必然产生对此种数据结构的依赖,模块独立性大幅下降。若将参数设计为简单变量,则失去了定义这个复杂数据类型的初衷,可理解性变差,反而使程序开发变得复杂。若将此数据类型作为公用类型,让所有模块都可访问,虽然既保证了函数参数的通用性,也方便了数据处理,但必然会受到这个公用数据类型的限制,这个数据类型的改动将涉及所有模块的代码重写,直接降低了程序的可扩展性能。
2 结构优化原则
该文探讨一种模块的优化组合方法,在面对复杂数据结构的情况下,能够提高模块的独立性。这种方法以总线式结构为基础,利用Microsoft.NET Framework的反射机制作,管理模块加载和配置,达到程序的深层次修改和模块多维扩展的目标。此方法遵循的原则是:(1)灵活的模块组合方式。模块组合的方法操作简单,结构灵活,方式多样,加载的方式必须一致。不仅要求模块可替换、可扩展,还能实现新老版本模块的交替使用。(2)明确的模块间相互依赖关系。所有模块不能依赖于下层模块里的类、属性、字段、方法等,只能依赖上层模块。所有模块之间的关联,必须是松耦合关联,并且关联方式必须通过顶层模块调用。(3)简洁的接口设计。简化总线和固定模块的功能,减小接口的数量和规模,强化组件、工具的功能,工具的使用必须采用一致的接口调用。
3 程序结构设计优化
根据软件的业务逻辑模型,将模块各部件封装成类,变为独立的整体单元,自上而下的将模块组合设计为层次结构,可用树形结构描述。树形结构的根节点位置模块,就是系统总线。总线并不负责所有子模块的加载,子模块的加载工作交给另一个中间件完成。总线只是一个容器,提供所有模块的存储空间和总线资源的引用方法。将总线也设计为模块,若系统功能改变,总线也可以像其他子模块一样,进行替换。模块加载中间件逻辑上位于两个模块中间,实现宿主模块和多个子模块关联。它为宿主模块提供子模块的加载方法,并规范子模块,使之符合被载入条件。为了叙述方便,将这个中间件命名为ModuleLoader,它继承于被命名为IBus的资源调用接口,让被载入子模块通过接口能够向上搜索,找到树形结构的根节点—总线,子模块通过总线调用其模块的各种功能。ModuleLoader的核心部件是其内部维护了一个可加载类型的配置列表,当向模块中插入下级模块时,只需要向此表中添加一个类型,由ModuleLoader自动完成对这个类型的所有继承类型的扫描,并选择符合条件的加载类型,进行实例化。软件框架搭建时,让所有可扩展的模块基类都继承于ModuleLoader类型,模块的扩展采用继承的方法。这样,所有的模块都具备载入其它模块和被载入的功能,并且载入的模块可以被选择和配置。开发者可以实现任意两个模块之间的从属结构关联,从而构成复杂的程序框架结构。
4 ModuleLoader的设计
ModuleLoader的关键功能是实现模块的动态加载。其原理是它實现了对模块子类的扫描功能,这种扫描功能利用了.NET Framework提供的反射机制(Reflection),该项可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件,以及构造函数等。树形结构中的每个ModuleLoader中都内含扩展类型配置集,利用反射的功能,扫描配置集中类型的子类信息,检查配置状态,当条件符合时,创建这个类型的实现,加入到实例列表中。当需要扩展一个模块时,只需要修改其宿主模块的配置集信息,ModuleLoader会自动搜索到这个类,实例化该类型,替换原有模块。ModuleLoader由两部分组成:(1)配置工具部分包括:用于存储所有的可扩展类型和状态的集合、用于获取模块子类信息的扫描工具、用于自动创建配置集信息的构造器和用于配置集信息设计和获取的配置器。(2)实例化工具部分包括:用于保存所有子模块实例的对象列表、用于根据配置集信息创建模块实例的构造工具、用于增加、删除、替换模块实例的管理工具和用于获取用户所需子模块实例的对象提取工具。这两大部分协同操作,使ModuleLoader不仅可以加载、管理不同类型的多种模块,也可实现对模块不同版本的交替使用,使程序框架中模块的组合方式更加灵活。
5 命名空间的依赖性
为了提高模块的独立性,消除模块间的病态关联,必须明确规范模块间的相互依赖关系。利用命名空间的可视范围来限制模块间相互依赖的规范方法是可取的。因为下层命名空间的对象可以访问上层命名空间的类型,而上层命名空间的对象是无法访问到下层以及同层其他命名空间的对象资源的。根据软件结构的逻辑关系,确定命名空间的层次,顺序,范围。当结构下层的模块使用另一个命名空间的模块时,必须通过ModuleLoader访问顶层的IBus接口调用其它模块对象。并且规定不准使用对下层或同层其他命名空间的直接引用。这样的规范方法,能够保证所有调用都通过顶层总线,避免了模块之间的非逻辑性关联,在一定程序上提高了模块的独立性。
6 接口设计
若说命名空间为模块之间的访问设置了阻碍,则接口设计就是在不可视的模块之间搭建了相互使用的桥梁,让其间产生了相互使用的可能。所以模块的接口应位于命名空间的较高层次,確保特定功能范围内的可视性。接口一旦定义,一般不轻易修改。因此,为通用性高的核心固定模块设计接口,将使用率低,通用性不高的功能设计为工具,所有工具只设计一种接口的方法较为合理。工具集内部也可以按功能设计为层次关系,并用动态加载的方式进行管理。但工具的使用必须采用统一的接口调用方法。这样的设计能够减少接口的数量,控制固定接口的规模,减少接口扩展时的代码编写工作量,从而达到优化程序结构的目的。
7 数据结构调用
数据结构是决定模块独立性的重要关键问题。在专业软件中,多种基本数据类型聚合为复杂的数据类型,这种复杂数据类型符合软件业务功能的逻辑模型,能够便于被各模块分析、处理。但是数据类型一经定义,很难扩展。当大量模块都使用此种数据类型时,增加、改变其中的一个变量的名称就可能涉及大量模块的改动。所以数据类型经逻辑分析组装好后,也可为其设计接口,模块通过接口调用复杂数据类型的对象。只要接口不改变,即使数据类型变化,所有的模块还能通接口调用的方式,对数据类型继承访问。但使用这种方法时要注意两个问题:(1)若在子模块中创建复杂数据类型的实例时,应利用ModuleLoader提供的方法动态创建数据类型的实例,并转换为接口类型使用。(2)接口中可能会变化的属性,最好使用泛型集合类型定义。模糊化了的属性类型,能够保证数据类型的扩展,不会某些固定类型的限制,否则也不会达到数据类型可扩展的目的。
8 效率分析
由于对象的多层次引用和动态创建,配置集的频繁搜索,大量的封箱、拆箱操作等,必然会牺牲软件运行的时间和空间效率。但用这种牺牲换取的是良好的模块封闭性和结构的紧凑性。而且,由于复杂的动态管理功能被封装在ModuleLoader里,所以在开发、使用层次的实际操作变得十分简单,牺牲运行效率的过程一般发生在初始化环境、事务高度和模块运行的起始环节,在飞速更新的计算机硬件上已经显得微不足道。实际上,工程师更关心软件的结构、扩展性和局部操作性能(如空间数据的显示、处理效率),只有对局部模块内部的功能不断优化、升级、扩展,才能真正提高软件的运行效率,而这样的优化和升级必须建立在良好的结构基础之上的。
9 结语
开放的结构、灵活的扩展方式、弹性的组合方法让软件框架结构的组织变得更加清晰、紧凑,延长了软件的生命周期。结构设计让程序开发工作能够产生良性的优化机制,让开发者能够始终站在巨人的肩膀上,精益求精,不断创造奇迹,让工程师有精力将注意力由开发技术研究转化为开发工艺研究。云计算的时代即将到来,优化模块结构不仅适用于桌面应用程序,也适用于云端服务单元的创建,在“软件—服务”这一思潮涌来之时也将大有作为。
参考文献
[1] 马蕾.基于改进身份认证协议的单点登录系统研究[J].微电子学马计算机,2010,29(7):180-183.