OO第四单元作业——UML指令的解析与查询
OO第四单元作业——UML指令的解析与查询
本单元作业的任务是实现UML图有关指令的解析与查询。对于设计好的一张UML图(可以是类图、状态图或顺序图),使用官方提供的解析包将其解析为UML指令序列。我们的设计目标是:根据这些指令,构建一张虚拟的“图”,用于后续的查询操作。在第15次作业中,将图构建完毕后,还要检查其正确性,即输入的UML指令序列是否代表了一个没有逻辑错误的UML图。
一、第四单元架构设计
本部分主要阐述虚拟“图”的构建思路。
1、基本思路
何为虚拟“图”?它是我们的程序在运行的时候,根据输入的UML指令,构建的一张图。在这张图构建完毕后,首先检查其有没有逻辑错误,然后便开始执行查询指令。因此,这张虚拟“图”的构建方式尤为重要,它决定了后续的查询过程是否能够顺利进行。
虚拟图根据UML图的类型,也分为三部分:类图、状态图和顺序图。每个UML元素均成为相应类型的图中的一个节点,即类图中的节点包括UmlClass,UmlOperation,UmlAttribute,UmlParameter,UmlAssociation,UmlAssociationEnd,UmlInterface,UmlGeneralization;状态图中的节点包括UmlStateMachine,UmlRegion,UmlPseudoState,UmlState,UmlFinalState,UmlTransition,UmlEvent,UmlOpaqueBahavior;顺序图中的节点包括UmlCollaboration,UmlInteraction,UmlLifeline,UmlMessage,UmlEndPoint,UmlAttribute。
我设计了三个Java Package,分别用来解析这三张图。
2、解析器的解析过程
在MyImplementation类中进行UML元素分类,将所有涉及UML类图的元素按顺序传入类图解析包的入口类中。对于一张UML类图,使用官方包将其转换为UML指令的集合,但是如果我们按照导出指令的顺序来解析,很可能会比较麻烦。比如,若第一条指令是UmlOperation,而第二条指令是UmlClass,而且它们还存在从属关系,那么我们在解析完这两条指令后,还需要再次扫描这两条指令,确定它们之间的关系。因此,我们有必要考虑这张虚拟图中的节点的建立顺序。
对于类图来说,一个元素对应的指令必须在其父元素对应的指令之后解析。比如,对于一个UmlOperation指令,它的父元素对应的UmlClass指令必须在该指令之前解析,使得在解析UmlOperation指令的时候,可以直接按照父元素id找到图中的父节点。因此,我对于类图的解析顺序如下:
1、UmlClass
2、UmlInterface
3、UmlOperation
4、UmlParameter
5、UmlAttribute
6、UmlGeneralization
7、UmlInterfaceRealization
8、UmlAssociation
9、UmlAssociationEnd
在这些元素全部解析完毕后,需要确定AssociationEnd所指向的引用类型。而后,根据UmlAssociation两端的连接情况,在相应的UmlClass或UmlInterface中记录与它相关联的那些类或接口就可以了。由于类节点与接口节点的极大相似性,在我的设计中,它们都由同一个一般化的节点继承而来,称为“实体”节点。
以上所述为UML类图有关指令的解析过程,UML顺序图与状态图的解析过程类似。
我们需要注意一点:UmlAttribute这个元素同时出现在类图与状态图中,那么我们如何分辨这个元素到底属于类图还是状态图呢?通过匹配UmlAttribute元素的parentId字段,我们可以确定其父元素的类型。若其父元素为UmlClass,则它是类图中的元素;若其父元素为UmlCollaboration,则它是顺序图中的元素。
二、对OO架构设计的理解
通过一个学期的学习,我对OO架构设计有了初步深入的理解。对于一个代码规模较大的编程任务,我们可以采取两种编程方式:自底向上和自顶向下。
“自底向上”指首先从代码可实现的角度分析并实现问题的细节,而后逐步把眼光从局部转移到整体,整合并修改这些已经实现的细节,来解决整个问题。这种编程模式是我在学习《数据结构》的时候经常采用的。那时候我们遇到的问题都比较简单,看完题干后就可以在脑海中梳理出解题的步骤。我们只要设计出相关数据结构的操作函数,而后在主函数中与题干进行一下对接就可以了,不需要有什么架构设计。
“自顶向下”指首先设计架构,而后填补和实现细节。这是OO这门课要求我们练习的编程方式,也是以后的项目中经常需要的实现方式。在这门课中,我们要练习的能力就是如何根据一个复杂的情景问题,来设计出一个易实现,易扩展的架构。
第一单元的问题情景是表达式的解析与化简。我们的设计架构是“表达式——项——因子”递归下降解析法。在解析表达式时,根据“+”、“-”符号将表达式切割为项,在接下来的解析中,若按照当前的分割能够解析成功,就继续向下解析,否则尝试另一种解析方式。
在第二单元的多线程电梯调度设计中,调度器的设计是任务完成的关键。有多种不同的调度器设计方式,我选择的是为每一部电梯设计一个调度器,每个调度器对应一个线程,同一楼层或同一楼座的多部电梯对应同一个等待队列。也就是说,多个调度器线程会同时访问等待队列,这是典型的“生产者——消费者”模型。
在第三单元和第四单元的设计中,框架的设计自由度较小,但是对框架的理解仍然十分重要。这两次作业均采用数据结构中的“图”来模拟一个网络,于是我们的重点就是搞清楚网络中的节点和边在图中的设计细节。我们还需要做到将图的构建部分与图的查询部分完美契合,使得对图的查询操作的复杂度尽量降低。
三、对测试理解与实践
测试在OO大作业的完成过程中显得十分重要。由于中测的强度不够,很难发现一些隐蔽的问题,因此只有自造评测机,进行大量数据的测试,才能发现这些问题。未经过大量测试的程序在强测中很难拿到高分。
在第一个单元,我没有意识到测试的重要性,在手动debug的过程中十分痛苦。而且因此在第三次作业中挂了中测。于是在第二个单元,我开始花费大量精力制作测试程序。终于功夫不负有心人,在第二单元的三次作业中,我强测的成绩全部在90分以上,另外我的测试程序还能够帮助其它同学,可以这个单元的测试是十分成功的。
我在后面两个单元都没有做测试程序,对拍工作也做的不够,因此强测得分都不高。若说为什么不做测试,我可以说是我的眼睛长时间盯着电脑很疲劳,后来竟导致我对OO、OS这两门课产生了厌学情绪,但这终究是借口,真正的原因是我不知道该怎么生成数据。
第二单元的任务可以抽象为:给出顾客要到达的目的地,我的程序需要分析顾客为了到达目的地需要行进的路线。那么在测试程序中,只要分析一下按照我给出的路线规划,是否能让全部顾客到达它们的目的地,而且在途中没有一些违规状态就可以了。生成数据也十分简单,只要生成随机数量的顾客,而后为每个顾客随机选择出发地和目的地即可。但是在第三、四单元中,其任务可以抽象为:先建立图模型,而后对图的结构进行查询操作。这两单元的测试手段主要是:生成随机化测试数据,而后和同学对拍。由于我没有自己生成的数据,于是只用了几个同学生成的数据来测试了一下,当然效果甚微。
四、课程收获
这个学期最大的收获其实就是上面说到的两点:对架构设计和测试的理解和实践。
每单元作业的三次迭代开发使我有改进和优化架构设计的机会。可以说,在这四个单元的第三次作业中,我的架构都能基本做到功能的单一性、可扩展性和代码的易读性。为了达到这个目标,我每次作业都是在上次作业的基础上进行大范围的整改(因为实现思路没有问题,因此不需要重构,而是对大量细节进行了修改)。目前,我已经可以对一个规模复杂的问题进行高效的架构设计了。
在测试上,我的工作并不令自己满意。我只有第二单元的测试可以说是比较成功,另外三个单元在测试上做的工作明显不够。我计划在暑假中重新温习第一、三、四单元的作业,练习测试程序的设计。
五、课程改进建议
1、多提供一些预习资料。
我在上个学期有java学习基础。这学期寒假的预习教程中的10倒题,我花了两个下午就全部完成了,因此在学期初有些轻敌,结果付出了惨重的代价。我建议增加预习教程的难度,迫使同学们在假期多花些功夫。
2、中测数据透明化,帮助同学们发现一些明显的问题。
只有把过于明显的问题结果,我们的程序才可以说是步入正轨。大家一般是在中测完成后,确定源程序没有问题后,才开始制作测试程序。若是挂在了中测上,很可能是一些简单,但是没有考虑到的问题。
3、强测数据增加。
助教曾经透露,课程组提供了很多强测数据,对同学们进行测试后,选择通过率最低的几个发布。我建议在不改变算分规则的前提下,将更多的强测数据项同学们开放,便于进行进一步的debug。