杜 莹,龚桂荣,陈 刚
(信息工程大学 测绘学院,河南 郑州 450052)
三维地形环境仿真系统经过多年的发展,已经从仅支持简单的局部地形浏览,演进到支持面向全球的多源异构、多时相数据,甚至可以作为一种基础平台,在其上搭建以空间和时间为基准的各种仿真应用系统。但是,如果基础平台的架构设计不合理,各模块间耦合度高,就极有可能给软件开发带来诸多不便甚至危害。因此,需要在平台设计过程中充分考虑此类问题,这样才能得到一个高效、高质的仿真平台。而解决上述问题的一个有效办法,就是设计模式。
设计模式是软件复用技术中的一个重要概念。它为开发者提供了一种考虑问题、解决问题的方式,其目的是将软件开发中成功的经验予以总结并推广,以求在相同的问题领域中,能够使其他设计人员从中得到启发和帮助[1]。
随着技术的不断完善,设计模式的种类日益增多,相对于Gang of Four(GOF)1995年提出的23种通用的设计模式,数量已经大大增加了。要从如此多的模式中选择适合自己系统的模式并非易事,选择正确、恰当的模式成为设计模式的使用瓶颈[2]。因此,必须先认真分析系统本身的特点和需求,总结需要解决的问题,有的放矢地寻找若干简单有效的设计模式,避免出现“反模式”和“错误模式”。
基于松耦合的设计原则,本文为三维地形环境仿真平台设计了组件化的体系架构(见图1),其基本思想是“分治法”,即将系统分解成可独立开发的松耦合组件,使其在可靠性、重用性和可扩展性等非功能性问题上有良好表现[5]。
图1 三维地形环境仿真平台的体系架构
其中,主控层负责内部的各种交互与控制,疏通仿真平台与各功能组件的通道,同时松散各组件之间的耦合度;功能组件层以接口形式为平台提供各种功能,其相互间的交互通过主控层完成;数据访问组件作为一个特殊的组件,负责完成地形数据的调度与存取。
这种层次化、组件化的体系架构清晰明了,非常便于分工合作。但要真正把这种体系架构落实到应用,还有几个关键环节需要设计好,这也是本文着重要解决的几个问题。
问题1:主控与各个功能组件之间如何进行通信。主控与各个功能组件之间是一对多的关系,所有的消息、命令和交互等都要通过主控分发到相关组件,并由组件做出响应。考虑到三维地形环境仿真系统实时性、交互性强等特点,这种分发和响应工作会非常频繁,处理不好就会制约后续事件分发和响应的进程。
问题2:各个功能组件如何通过地形访问组件获取数据。在图1所示的体系架构中,很多组件都需要与地形数据发生交互,如三维地形组件需要调度地形数据进行绘制,三维分析组件需要获取高程数据进行坡度、通视等分析,三维相机组件需要获取当前视场范围内的最大高程值,以便进行碰撞检测,等等。如果这些组件都直接去访问地形数据库或数据文件,不仅会造成大量的代码冗余,一旦数据格式发生改变,还会引发多个组件的多处改动。这就是图1中地形访问组件的设计初衷,即通过统一的渠道访问地形数据。
问题3:如何应对多源异构的地形数据。随着数据获取手段的丰富,地形数据尤其是地形纹理数据趋于多样化,可能来源不同,如来源于不同的传感器、不同的拍摄日期、不同的分辨率等;也可能存储方式不同,如存储于本地文件、本地数据库、远程服务器等。诸多不同导致地形访问组件面临巨大的考验,如何才能做到“以不变应万变”,不随数据源的改变而颠覆以往的设计和接口。
上述3个问题虽然侧重点不同,但它们有着一个共同特点,即都属于非功能性问题,都涉及到软件的灵活性和可扩展性。显然,设计模式是解决该类问题的不二选择。
然而,设计模式的描述主要是以自然语言结合OO框图的描述方法,一些语言描述有一定的歧义性,使设计者在为特定问题选择设计模式时往往会犹豫不决。虽然设计模式有许多优点,但是仍然需要人们掌握它的本质并学会正确的使用方法,才能够正确应用并真正发挥它的优势,同时应该避免滥用带来的软件结构的复杂性和冗余,不能为了使用模式而使用[2]。
鉴于设计模式的复杂性,作者对第2节中提出的3个问题进行了抽象,并按照设计模式的分类方式进行了归类和匹配,从而提出分别用职责链模式、代理模式和桥接模式解决这3个问题。
三维地形环境仿真系统具有实时性、交互性强等显著特点,如用户在场景中的鼠标操作需要由主控分发到多个组件,场景的相机位置发生改变需要通知到多个组件,等等。如何处理好主控与各个功能组件之间这种复杂的请求与反馈关系呢?作者经过分析,选择了“职责链模式”来解决这个问题。
职责链模式(Chain of Responsibility Pattern),是指使多个对象都有机会处理请求,同时避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止[3]。这种模式应用很广,它的最大优势是接收者和发送者都不需要对方的明确信息,即可完成请求的发送和处理。如图2所示,为职责链模式的UML图。
然而,图2只是职责链的标准模式,它虽然具有很好的扩展性,可以使软件具有动态增加请求处理者的能力,但却存在着两个很现实的问题:
图2 职责链模式的UML图
1)每次请求最多只有一个Handler能接受处理,其它Handler都是无用实例,如果一个请求到链的末端都得不到处理,或者因为没有正确配置而得不到处理,就会导致所有Handler都成无用实例。
2)在某些特殊情况下,可能有多个Handler需要接受处理,也有可能某些Handler允许或不允许其后继者继续处理请求(如在响应键盘事件时,某些组件试图锁定某些热键,不允许其后继组件使用,但并非锁定所有按键),图2中的标准模式是无法解决这个问题的。
针对这两个问题,作者对图2中职责链的标准模式进行了改进,如图3所示。
图3 改进后职责链模式的UML图
图3主要做了如下两点改进:
1)针对问题1,使用发布-订购(Publish/Subscribe)模式,只让对相关请求“感兴趣”的组件并实例化,这些组件需要预先订购这些请求,本文将其由“Handler”更名为“Subscriber”(订购者)。这样,在发送请求时,3D主控只需要面向订购了该请求的组件,也就不会出现无用的Handler了。
2)针对问题2,为Subscriber的接口函数HandleRequest()增加一个bool型参数lock,表示当前处理者是否允许其后继者继续处理该请求,如果lock为true,表示当前组件锁定该请求,职责链不再向下传递请求;否则,请求继续传递,直到lock再次为true或职责链走到末端。这样就解决了多个Handler需要处理请求或锁定请求的问题。
在三维地形环境基础平台中,很多组件都需要与地形数据发生交互,如果这些组件都直接去访问地形数据库或数据文件,不仅会造成大量的代码冗余,一旦数据格式发生改变,还会引发多个组件的多处改动。如何确保各个组件能通过统一的渠道访问到地形数据呢?作者经过分析,选择了“代理模式”来解决这个问题。
代理模式(Proxy Pattern),是指为其它对象提供一种代理,以控制对这个对象的访问[3]。如图4所示,为代理模式的UML图。
图4 代理模式的UML图
DBProxy接口:定义了DB(数据库)和DBProxy(数据库代理)的共用接口,这样在任何需要使用地形数据(即需要调用DB)的地方都可以使用DBProxy,所有需要与地形数据交互的组件只需要通过统一的DBProxy接口即可,而无需了解DB本身的格式或格式是否发生了变化;
DB类:定义DBProxy类所代表的真实实体;
DBProxy类:保存一个引用使得代理可以访问实体,并提供一个与DBProxy的接口相同的接口,这样代理就可以用来代替实体。
与2.1节中讨论的职责链模式相比,代理模式的思路和实现方法都比较简单,唯一需要注意的是,由于需要确保通过统一的接口访问地形数据,因此,必须结合“单实例模式”,确保整个系统运行过程中无论有多少客户端,DBProxy都只能有一个。
单实例模式(Singleton Pattern),是指保证一个类仅有一个实例,并提供一个访问它的全局访问点[3]。其核心思想是让类自身负责保存它的唯一实例,这个类可以保证没有其它实例可以被创建,并且它可以提供一个访问该实例的方法。图5为作者结合单实例模式后的代理模式。
图5 改进后代理模式的UML图
一个具有很好通用性的三维地形环境基础平台,需要能支持多源异构的地形数据,而仅仅依靠代理模式是不够的。因为该模式只是解决了如何通过统一接口访问地形数据的问题,至于数据库代理(DBProxy)如何取到这些数据,数据是什么结构,存储于什么位置,这不是数据库代理能够解决的问题,也并非其职责所在。作者经过分析,选择了“桥接模式”来解决这个问题。
桥接模式(Bridge Pattern),是指将抽象部分与它的实现部分分离,使它们都可以独立地变化[3]。当一个抽象可能有多个实现时,通常用继承类协调它们,从而将抽象部分与其实现部分紧紧耦合在一起,难以对抽象部分和它的实现部分独立地进行修改和扩充。而桥接模式则通过将接口和实现部分分离,提高了可扩充性,同时还可以将实现细节对用户透明[4]。如图6所示,为桥接模式的UML图。
图6 桥接模式的UML图
其中:Abstraction接口表示抽象部分,Implementor接口表示实现部分;RefindedAbstraction类表示被提炼的抽象,ConcreteImplementorA和ConcreteImplementorB表示具体的实现。
利用桥接模式的优势,作者设计了一种基于桥接模式和交换模型的多源异构数据获取方式(见图7所示),基本思路如下:
1)定义一个数据提供者接口(DataProvider),为数据库代理组件(DBProxy)提供各种来源的地形数据。通过这种方式,在DBProxy和各个异构数据源(如图7中的DataProvider1和DataProvider2)之间架起了一座“桥梁”,从而避免了数据库代理直接访问各个数据源,实现了代理与数据源的松耦合,这就是桥接模式的主旨所在。
2)定义交换数据模型。如图7中的TransModelDem和TransModelImg,分别表示交换模型中的Dem数据和纹理数据。设计交换数据模型的目的,是为了使各个DataProvider能在根据指定的参数(如地理范围、分辨率等)获取到自己内部格式的数据模型后,将内部格式转换为一种统一格式的数据模型,返回给数据库代理。这种统一格式的数据格式,就是作者所设计的交换数据模型。它实现了数据库代理组件与各异构数据源之间的松耦合,达到了减小组件粒度,提高重用性的目的。
图7 基于桥接模式的数据获取结构图
设计模式是软件工程中的重要研究方向,它能有效解决软件设计中的可复用性和可扩展性等问题[6]。本文首先分析了三维地形环境仿真平台的特点,然后将设计模式引入平台的设计,分别用职责链模式、代理模式和桥接模式解决了平台设计中存在的3个主要问题。实践表明,设计模式可以有效提高软件的可维护性和可扩展性。
[1]杨雪榕,梁加红,冯向军,等.分布式仿真软件三层设计模式及应用[J].系统仿真学报,2008,20(21):5812-5815.
[2]林舒萍,罗键.设计模式的应用研究[J].计算机工程与设计,2005,26(11):2980-2982.
[3]程杰.大话设计模式[M].北京:清华大学出版社,2007.
[4]涂建光,边馥苓.基于设计模式的组件化GIS软件开发方法[J].武汉大学学报:信息科学版,2005,30(1):77-81.
[5]Kim D H,Kim K S,Choi H.The Design and Implementation of Open GIS Service Component.Geoscience and Remote Sensing Symposium,2001,4:1922-1924.
[6]Eric Freeman,Elisabeth Freeman,Kathy Sierra,Bert Bates.Head First设计模式[M].北京:中国电力出版社,2007:236-372,316-381.