BUAA_OO第四单元总结性博客作业——UML(Floyd实现规则检查?)

一、架构设计

1、UML第一次作业——类图

  第一次作业基于不同element在UML规格中的从属关系来设计架构。继承了UmlInteraction接口的MyUmlInteraction类是主要的交互层。在其构造函数中对加入进来的每一个element进行分析,判断它的ElementType并根据parentId以及其他解析出的性质决定它的存储位置。

对于不同的ElementType,我将其分为三类:

  第一类是有所述其他元素的,代表java中具体的类的element,如UmlClassUmlOperationUmlInterface等,它有一些自己的属性,在UML中有自己的子类,我就为它们建立了MyClassMyOperationMyInterface三个类,用于存储element自身的基本信息以及UML图中其他的属性,并用HashMap存储id、name与具体的类之间的映射关系。如:根据UmlClassUmlOperation之间的从属关系,MyClass类中也需要存储所有对应的MyOperation类;根据继承与关联关系,MyClass类与MyInterface类中也有存有对应的父类信息以及关联类信息。

  第二类是ElementType形如UmlParameterUmlAttribute的element,它除了基本属性之外并没有其他属性,并且在UML架构中能找到它们所述的父类,在存储是就直接存在对应的My***类中,如UmlAttribute存在对应的MyClass类中,。

  第三类是代表继承关系、实现关系以及关联关系的element,如UmlInterfaceRealizationUmlAssociationUmlGeneralization,对于这样的element,我先将其存放于对应的数组中,待所有的element都存储在对应的类中后,再调用不同的方法对所有的关系进行分析,将结果存入不同的类中。

  MyUmlInteraction交互层中,对于每一个作业要求实现的方法,都只要根据HashMap映射先找到输入classname对应的class,再调用MyClass类中对应方法返回结果或其他异常报错信息。对于与父类有关方法的处理,则先调用Class自身的方法,再调用parentClass的对应方法,若parentClass还有父类,则递归调用parentClass的父类的方法;如此往复递归调用,逐级返回信息并输出。

  作业架构:
  

2、UML第二次作业——类图、状态图与顺序图(Floyd算法可行性?)

  第二次作业在第一次作业的基础上,增加了对于顺序图以及状态图的处理,说白了就是增加了更多种类的element,需要我们进行更全面的分析。

  我使用继承的方法完成第二次作业的交互类——MyUmlGeneralInteraction类的实现。具体方法是,直接拿上一次作业的MyUmlInteraction类,实现UmlClassModelInteraction接口作为父类;而MyUmlGeneralInteraction类,实现UmlGeneralInteraction接口,继承MyUmlInteraction类作为子类,这样对于类图的所有元素以及相应方法(UmlClassModelInteraction接口中定义的方法)的处理就都留在了父类中,而对于第二次作业新增的方法都放在子类中来实现。在实现MyUmlGeneralInteraction类的构造方法时,先使用super()调用父类的构造方法,完成对类图主要元素的分析,在子类中再遍历一遍传入的elements数组,分析与状态图、顺序图有关的element并做好相应的存储。

  这次作业我新建立了三个与状态图有关的类MyStateMachineMyRegionMyTransition(其实只要一个MyStateMachine类就能实现全部功能,但一开始分析架构的时候发现这几个element都有其他属性,都写上之后就懒得改了),以及一个与顺序图有关的类MyInteraction。其中,状态机MyStateMachine存储1个MyRegion类,MyRegion类存储各个statetransition信息,并且还存放一个所有state的转移矩阵,用floyd算法计算后继状态;MyInteraction类存放顺序图信息,包括lifeline以及各个message,由于这次作业对状态图以及顺序图内容做了简化,实现起来较容易。

  第二次作业相较第一次作业还加入了模型有效性检查部分,这一部分我也用了Floyd算法来实现……

扫描二维码关注公众号,回复: 6567306 查看本文章

  其中UML002规则主要在class内部实现,对每一个class返回一个存储不符合规则的attributeNamehashset,再遍历所有class获取所有hashset信息,若hashset不为空,则会触发UML002检查错误异常。

  而对于UML008以及UML009两个异常,则是在classinterface之间进行,需要考虑实现与继承关系。我单独建了一个MyClassGraph类,存储所有的UmlClassOrInterface,以及所有的UmlGeneralizationUmlInterfaceRealization,并有两个存储异常信息的hashset。若类A继承了类B、类A实现了接口B,均视为有一条A到B的路径。以此为依据建立一个n*ngraph矩阵(n为UmlClassOrInterface个数)。

在初始化这个矩阵时,先让所有graph[i][j] == INF,表示映射下标为i的类到映射下标为j的类不存在路径(不存在继承或实现关系)。

  再将所有的UmlGeneralizationUmlInterfaceRealization信息填入矩阵。若类A实现了接口B或类A继承了接口B或接口A继承了接口B,则将A对应的indexi与B对应的indexj位置填上1,即graph[indexi][indexj]=1,表示A到B存在路径。需要注意的是,若在向A对应的indexi与B对应的indexj位置填上1时,graph[indexi][indexj]已经为1了,则表示发生了重复继承,需要将类(接口)A的信息存入存放重复继承异常的hashset中,同时将A存入一个特殊队列中,因为所有继承自A或实现了A的类(接口)也存在重复继承问题,需要在floyd运算结束后将所有存在到A路径的类(接口)都存入存放重复继承异常的hashset中。

  接下来用Floyd算法跑一遍这个矩阵。其中,在判断( graph[i][j] > graph[i][k] + graph[k][j] )更新路径的同时,还需要加入一个特殊的判断。即( graph[i][j] < INF && graph[i][k] + graph[k][j]) < INF ),这个条件的含义同样也是存在两条从i下标对应的类到j下标对应的类的路径(一条是从i到j的路径,一条是从i到k,再从j到k的路径),需要将i下标对应的类存放到存放重复继承异常的hashset以及特殊队列中。

floyd算法完成后,graph矩阵就变成了一个任意两个类(接口)之间的可达性矩阵。这时候若对角线上的元素值不为0,则代表存在一个类到一个类本身的路径,也就出现了循环继承问题。遍历一遍所有对角线上的元素,将不为INF的元素下标对应的类存入存放循环继承异常类的hashset中,这也就完成了UML009规则的检查。

  还有最后一个步骤就完成了UML008规则的检查,除了上述已经加入存放重复继承异常的hashset中的类之外(这些类也都在那个特殊队列中),我们还需要遍历一遍特殊队列,将特殊队列中的每个类对应下标(indexj)的那一列上的所有元素都做一遍判断,若graph[i][indexj]<INF,则表示i下标对应的类到indexj下标对应的类有一条路径,依据所有继承自A或实现了A的类(接口)也存在重复继承问题,我们也需要将i下标对应的类也加入存放重复继承异常的hashset中。

由此,UML008规则检查出异常的类就全都存放在存放重复继承异常的hashset中,UML009规则检查出异常的类就存放在存放循环继承异常类的hashset中。MyClassGraph类只需要返回这两个hashset到MyUmlGeneralInteraction类中,便能完成后两个规则的检测。

  作业架构:

  

二、四单元中架构设计及OO理解方法的演进

  第一单元多项式求导作业是刚刚接触OO所写的第一个系列作业,第一、二次作业可以说就是按照面向过程的思路来写的,采用逐项匹配的思路,针对每一项构建正则表达式分别求导,这个不成熟的设计思路也为后面的嵌套表达式挖了个大坑。第三次作业基本完全重构了一遍。第三次作业迫不得已采用了面向对象的一点点思想,将每一个因子看作是一个独立的对象,逐因子匹配,对每个因子本身求导,若是嵌套因子则递归向内部求导,逐层嵌套,最后再将求导结果合并输出。由于第一单元对面向对象以及代码架构的理解还不够熟悉,第一单元的作业炸了很多次……

  第二单元电梯系统作业我们接触了java多线程,也是我第一次尝试使用OO的思想来设计代码,具体实现方式为将输入线程、电梯服务线程以及中间的存储类分离,各司其职,并通过锁与wait-notify机制确保线程安全,也就是所谓的生产者-消费者模式。这一个架构我用了三次作业,每一次作业只需要在前一次作业的架构上稍作修改,增加一些新的功能即可。第二次作业在第一次作业的基础上,修改了电梯线程模块,增加了每一层捎带的功能;第三次作业在第二次作业的基础上在存储类中增加了一个调度器,负责分配请求到各个电梯的请求队列。在三次作业中持续运用并且不断成熟的架构设计,使得我代码出错的概率大大降低,也让我对代码功能的实现有了更为清晰的理解思路。

  第三单元的JML系列作业,向我们介绍了一种契约式的编程思想以及JML规格,并且基于这个思想逐步实现了对一个地铁线路图的分析。在这一单元作业中,课程第一次采用了官方包的模式,我们只需要根据源代码中的JML要求,实现官方包接口中的方法,只要代码实现严格满足JML,就能保证正确性。从这一次作业开始,在助教大大们提供的官方包的引导下,我才逐渐认识到了架构的重要性。课程组为我们提供了现成的接口设计,直接推动我们按照课程组希望的方向设计我们的架构,让我们把重心放在了各个方法的设计以及规格的实现上。架构与方法实现是相辅相成的,好的架构能让方法的具体实现更加游刃有余,避免了复杂凌乱的代码带来的灾难。

  第四单元的UML系列作业,则为我们介绍了一门全新统一建模语言——UML。这一单元的作业依旧采用了官方包提供接口,我们实现的策略。但这一单元的代码需要分析的信息更为复杂,不是简单地实现所有方法就能完成的,需要我们自己揣摩存储、分析数据的方法。经过前几单元的历练,这一单元我很快地找到了设计存储架构的方法,对于UML各个元素的分析也就显得比较直接,bug出错也大大减少。

三、测试理解与实践的演进

  第一单元自己写的代码架构由于一开始考虑得不周到,显得较为臃肿,也没有掌握测试的方法,只是依靠自己乏力地读代码以及一拍脑子想出来的测试数据去确保代码的正确性。对于复杂的多项式求导过程,这种测试远远不够,也就使得我第一单元三次作业炸了两次……

  第二单元对于电梯的测试,则是通过手动构造不同的测试集来检查电梯运行的正确性,尤其是第三次作业,为了保证换乘时的正确性,需要针对不同的换乘情况分别构造测试样例,来检查3部电梯的配合。测试样例的构造与代码的设计是相辅相成的,在设计代码时,就已经考虑到了可能出现的测试情况,在构造测试样例时联想到的代码可能无法实现的部分,又需要回去检查代码的设计。

  第三单元的测试就比较容易实现了,我在这一单元才使用对拍的方法测试代码。由于这一单元代码的架构已经由官方包规定好了,测试的重点就变成了是否严格符合JML的要求。我设计了针对某个功能的精确覆盖测试以及针对整个程序时间复杂度和鲁棒性的压力测试,在代码实现过程中就消除了大量bug,避免了强测互测被炸。

  第四单元的测试主要是通过画奇奇怪怪的UML图来测试自己的程序,考虑一些极端情况,在同学之间互相比对答案。由于画图的速度,第四单元的作业并不能进行大量的测试,但UML图本身固定的性质给代码的实现带来了很大的方便,极大地减少了极端情况的数量。其中比较坑的是第二次作业的规则检查部分,我们拿上一次作业的强测数据来测试第二次作业的代码,发现了很多对于循环继承和多重继承类判断的疏漏,及时发现了错误并进行修正。

四、课程收获

  从寒假刚下载idea,写pre作业时的一窍不通,到现在千行以上代码信手拈来,我在这一学期的OO课程中获得了很大的收获。

  首先最基本的就是java语言的初步掌握以及idea编译器的使用了,大一时候学习的都是C语言,大二下刚接触java语言时还是有点不适应了,但随着对java使用深入,我渐渐地发现了java语言的优缺点,认识到了c、c++以及java的不同。包括代码风格要求、代码架构、强测互测等,OO课程在一次次作业中也不断训练着我们的代码能力。

  其实就是贯穿课程始终的面向对象思想——一切皆对象。从最开始的糊里糊涂到发现面向对象思路的技巧与方便,我大概花了两个单元的时间。OO思想为我们分析代码实现提供了一个全新的思路,开拓了我们的视野。

  最后,我觉得OO课程这个以单元为划分,每一单元内的3次作业逐次递进的课程作业体系也锻炼了我们的架构能力。课堂上纪老师给我们多次强调了普通码农和架构师的区别。设计代码远远比实现代码重要的多,一个合理的代码架构能让我们的代码简洁易懂,也能大大减少错误的产生。OO课程在大二就为我们确立了架构的思想,对我们未来可能遇到的更复杂代码问题的解决会有极大帮助。

五、课程改进建议

  1.对于所有的通知,讨论区中标记的官方认证的答案以助教对疑惑问题的解答能否集中到一个公告贴中,不然每次都要翻遍全论坛的帖子确保自己代码没有遗漏什么点。

  2.实验课与理论课能否间隔几天,每次都是上午上课下午就上机,有几次没来得及看上午上课的内容,一头雾水地去上机……

  3.第一次作业的难度偏大了,感觉所有4个单元的作业中第一单元最后一个作业是难度巅峰,代码复杂度有点高。

猜你喜欢

转载自www.cnblogs.com/hkywwr/p/11067468.html