随着信息技术的不断发展以及企事业单位对管理信息系统的重视程度以及倚赖程度的加深,相应软件系统项目的开发也日趋庞大化和复杂化,软件系统的模块越来越多,但每个模块的基础部分的实现都很类似,比如对数据库总数据的操作、翻页功能的实现等等。通常可以结合已有的系统架构和公共模块,实现根据配置文件或者数据库定义来生成应用模块,然后在此基础上去进行业务功能的开发。
但这也会面临以下问题:(1)如果生成的配置文件或模板文件发生变化,那么相应的文件读取程序也要发生改变;(2)如果调用逻辑或调用顺序发生变化,那就要修改相应的调用程序;(3)如果输出格式发生变化,就只能重写代码来实现新的输出格式。类似这样的问题还有很多,这就要求有人专门负责维护这样的应用,不断更改程序来适应新应用的需要,但由于生成的调用过程、生成配置、生成模板等功能是混合在一起的,导致修改比较困难。
1代码生成器通用框架
为了解决上述问题,需要一个通用框架,来把生成应用的核心部分独立出来,形成公共的应用,让变化的部分从应用中分离出来,从而可以根据需要进行配置和扩展来适应各种应用的需要。该框架能够按照模版和配置去生成结果,支持模版和配置方式的自定义,支持生成方式的自定义,以及可以自定义生成的过程。该框架本身并不固定要求配置的格式,也不要求配置的来源,因而也不固定获取和解析配置数据的程序,完全由开发人员来制定配置的方式和格式,以及如何获取和解析这些配置数据等。
(1)分发调度模块。分发调度提供接受用户请求的入口,然后根据用户请求的内容,去获取相应的配置数据,获得户配置的数据过后,就按照配置的要求来发出命令,要求按照这些配置数据来完成generate的功能。在每一个具体命令的实现中,先动态组合需要完成的generate的功能,然后就把这些功能交给generate的代理去完成。也就是说分发调度只是负责接收用户的任务,然后把任务组合好,最后分配出去,只是起到一个调度的作用,本身并不处理用户的请求功能。
(2)生成代理模块。生成代理是一个介于生成调度和真正生成之间额外的附加层,目的是能够根据需要切换不同的代理,比如生成调度根据配置,需要通过远程来生成,那么就需要远程代理,远程访问的方式可能是rmi或webservice等等。
(3)具体调用模块。这个模块实现一个具体生成功能的调用过程,通常是把用户配置的参数数据,按照一定的规则与theme的模板相结合,从而得到需要生成的结果,然后把结果输出去的过程。
(4)模版管理模块。模板管理的功能就是负责获取相应的模板数据,并对这些模板的数据进行管理,在外部需要这些模板数据的时候,可以访问模板管理提供的接口来获取。
(5)生成输出模块。具体调用运行完成后,会产生生成的结果,这些结果如何输出?输出到什么地方?输出成为什么格式等等问题,都由生成输出来负责。生成输出本身并不固定任何的输出要求,开发人员完全可以在外部来定义输出的格式,输出的地方,以及如何输出等,只需要在theme的配置中注册新的输出格式即可。
(6)外部主题模块。外部主题是独立于核心框架之外的,完全由开发人员根据需要来制定,通常里面会包含生成所需要的所有原始模板文件,跟模板文件对应的解析和生成的辅助程序,生成处理的Action处理程序,还有theme的配置文件,一般这几个是必不可少的;其次是根据需要,由开发人员扩展的功能,比如:在进行生成处理前后需要额外添加的功能,自定义的输出类型等等。外部主题决定了按照什么来生成、如何生成以及生成为什么东西的具体信息,是提供给核心框架使用的重要数据。
(7)配置管理模块。该模块首先用于获取用户配置的数据,配置的方式很多,要求除了框架自身提供的配置方式外,还要能支持用户自定义的配置方式。其次用于缓存用户配置的数据,同一份配置数据,在运行期间会多次使用,但是获取用户配置数据的动作就只需要一次就可以了,获取过后,就缓存下来重复使用。最后是用于对外提供接口,让其他模块通过这些接口来获取所需要的数据。
2配置管理模块分析与设计
2.1配置管理模块的功能边界
(1)配置管理模块不关心被访问的数据是怎么来的,它只是按照访问方式去获取这些数据,当然不同的数据格式有不同的访问方式。
(2)虽然需要支持自定义配置方式,但是需要配置的数据是一定的,只是配置的格式不同,访问这些配置数据的方式不同,但最后殊途同归,都需要配置管理模块提供相应的数据。因此这个需要配置的数据项是要统一起来的。
(3)配置管理模块不关心获取到的数据是什么,也不关心这些数据的含义,更不关心这些数据怎么用,它只是负责获取这些数据并保存起来。
(4)配置管理模块不关心谁来使用这些数据,也不关心外部获取这些数据后如何使用,它只是负责提供这些数据而已。
2.2配置管理模块的对外接口
(1)核心框架运行需要的数据。以xml配置为例,默认取名为GenConf.xml,在每次使用的时候根据需要修改或配置其内容。
(2)用户需要生成的模块的配置数据。比如用户想要生成一个模块内的增删改查的源代码,里面有每个具体功能的配置,而每个具体功能就是一个源代码文件。这个也需要在每次使用的时候根据需要来配置,并注册到核心框架运行配置里面去,每次生成主要配置的就是这类数据。
(3)外部主题的配置数据。在制作主题的时候就配置好,里面有这套主题需要的外部数据,和预定义好的功能配置,在每次使用的时候一般不需要配置或修改。比如缺省为xml配置,取名为ThemeConf.xml。为了让外部获取配置管理模块内的数据,提供相应的API(应用程序接口),也就是GenConfEbi。
2.3配置管理模块的内部实现
为了更好的理解配置管理模块的内部实现架构,因此先以一个最简单的实现结构为起点,采用重构的方式,逐步把相关的设计模式应用进来,从简单到复杂,应用多种软件设计模式以及让多种设计模式协同工作。
(1)根据对外提供的数据结构定义,制作出相应的数据模型。
(2)针对前面定义的API,提供一个最基本的实现,只需要满足最基本的功能就可以了,需要实现读取配置文件的功能,然后要有缓存配置数据的功能,最后就是实现API中要求的功能。配置管理模块的结构如图2所示。
3利用设计模式改进配置管理模块
(1)加入简单工厂模式。上面的实现虽然向模块外部提供了接口,可是外部根本不知道模块内部的具体实现,那么模块外部如何来获取一个实现接口的实现对象呢?简单工厂解决这个问题的思路就是在配置管理模块里面添加一个类GenConfFactory,在这个类里面实现一个方法,让这个方法来创建一个接口对象并返回然后把这个类提供给客户端,让客户端通过调用这个类的方法来获取接口对象。
(2)加入单例模式。如果GenConfEbo被创建多次的话,那么就会重复获取配置数据,浪费程序运行时间,并且每个GenConfEbo的实例都会缓存这些数据,浪费内存空间。同一个类里面,既有实现GenConfEbi要求的对外功能,又有内部实现需要的获取配置数据和缓存数据的功能,从类的设计上来说,这个类的职责太不单一了,应该分离一部分职责出去。此时可以通过使用单例模式添加一个类ConfManager,来控制实例的数目。
(3)加入桥接模式。按照功能要求,配置数据的来源是多方面,比如:xml、properties、txt、DB等等,这也就意味着需要有不同的获取数据的实现来对应这些不同的数据来源。对于模块外部的应用而言,并不关心配置数据是如何来的,只关心需要使用的数据,而且需要配置管理模块提供所需要的一切数据。这些数据的多少、数据的组合结构是可能发生变化的。这就导致出现了2个纬度的变化,一个是获取配置数据这边需要不断扩展,另一个是配置数据所构成的数据模型这边也需要变化和扩展。可以借助于桥接模式把获取配置数据这边,设计成为桥接模式的实现部分,而数据模型这边设计成为桥接模式的抽象部分,而且数据模型这边确实需要使用具体实现部分来获取数据。而ConfManager就相当于桥接的抽象部分的顶层实现,GenConfEbo就相当于使用基本的抽象部分的扩展部分,只不过这里采用的是对象组合的方式,而不是标准桥接模式中的对象继承的方式。
(4)加入解释器模式。如果xml文件的格式发生了变化,那么读取配置文件的程序就需要做出相应的变更,严重的时候,几乎相当于完全重写程序。那么怎么解决当xml的结构发生改变过后,能够很方便的获取相应元素、或者是属性的值,而不用再去修改解析xml的程序呢?要解决通用解析xml的问题,首先需要先设计一个简单的表达式语言,在客户端调用解析程序的时候,传入用这个表达式语言描述的一个表达式,然后把这个表达式通过解析器的解析,形成一个抽象的语法树。其次解析完成后,自动调用解释器来解释抽象语法树,并执行每个节点所对应的功能,从而完成通用的xml解析。这样一来,每次当xml结构发生了更改,也就是在客户端调用的时候,传入不同的表达式即可,整个解析xml过程的代码都不需要再修改了。
(5)加入备忘录模式。在根据字符串来构建对应的抽象语法树的时候,有很多字符串前面都是一样的,这样每次重复创建太浪费时间了,最好是相同字符串对应的树形对象就不用创建了,直接分析后面不同的部分,然后把新的部分添加到已有的这棵树里面。每次在拿到一个字符串过后,先跟已有的记录进行比较,找出最大的已经解析过的字符串,那么这个长度的字符串是不需要再解析的,只需要把后面没有解析的字符串拿出来进行解析,然后把解析的对象添加到已经解析好的这个对象后头就可以了。这就需要每次在解析的时候,每次解析完成,就向备忘录里面添加一条记录,而每次进入的时候,根据最长能匹配的字符串,从备忘录里面获取到相应的对象,这就不用解析了。
(6)加入生成器模式。当解析一个xml文件时,需要拼接很多很多的字符串,而这些字符串的拼接过程是十分类似的,只是最终拼接后的结果不一样,可以通过加入生成器模式来让这些字符串的拼接过程变得简单和统一。每个字符串都是从根节点开始,依次拼接到最后需要取值的地方,因此可以把这个拼接的过程统一起来,做成生成器,而调用这些生成器来构建最终产品的客户端,这个时候就充当了生成器模式中的指导者。
作者:蒋继冬 单位:南京工程高等职业学校