第四单元架构设计
第13次作业
这次作业是关于类图的解析和查询。在这次作业中,我的想法是将UmlClass和UmlInterface作为最顶层的节点,够成一张图,然后每个UmlClass和UmlInterface下面都有自己的操作、属性等等,按照树的形式将这些UmlClass和UmlInterface内的东西组织起来,然后操作下面再挂着参数等东西,基本上是按照类图的组织形式将这些元素组织起来。
在组织的时候,是直接在MyUmlInteraction中创建所有的对象,然后将这些对象放在相应的位置,以Class->Interface->Attribute->Operation->Parameter->Generalization->InterfaceRealization->Association的顺序组织起来。同时在涉及到到关联与继承关系的时候,我决定将这些关系放到UmlClass和UmlInterface内部,这样涉及到父类的查询,可以通过递归的方法来实现,而且可以将得到的结果存起来,避免重复计算,提高效率。
第14次作业
这次作业增加了对状态图和顺序图的解析和查询,以及对类图的规则检查,类图的解析和查询并无变化,所以在这次作业中,我将新增加的状态图和顺序图放到和已有类图的同一层次,实现起来会比较方便,但是如果要对类图、顺序图、状态图之间进行检查,可能会比较麻烦,但这是最后一次作业,所以暂时也不用考虑这个。而且状态图和顺序图可能会有多个,所以我把状态图和顺序图各自放到一个容器里面,这样便于管理。
顺序图的查询只是简单的计数问题,难度不大,状态图中关于状态计数和迁移计数也是比较简单,有点麻烦的是后继状态的计算。后继状态的计算其实和循环继承类似,在这部分,如果我继续使用上次作业的递归调用的话,可能会出现死循环的问题,所以我选择使用有向图来解决,可以根据连接关系(后继状态或者继承)来建立连接矩阵,然后使用Floyd算法或者其他算法来求得每个点(状态或类)到其他点的距离,各个点的连接关系就很容易找到,对于后继状态就是计算能到达多少个点,对于循环继承就是判断是否能够到达自身,复杂度可能会有些高,但是确实是很有用。
上面已经介绍了我对于UML008规则的检查算法,UML002的检查算法其实比较简单,我把这个算法的实现放到了Class这一层次,一方面是因为Class知道他有多少个关联,另一方面这样可以将问题层次化解决,一个类能知道自己是不是违反了这个规则,那么类图就只需要检查有没有类违反的这个规则即可,后期代码修改和debug会简单很多。UML009规则我是使用内置flag来解决,在上次作业中,我可以保证每个接口都知道直接继承和间接继承了哪些接口,我只需要在求解的过程中,判断是否有接口被重复继承即可,当有接口被重复继承,那么就设置该接口的flag,当其他接口继承或类实现的时候,就知道自己是否违反规则。
四个单元的架构设计和面向对象方法理解
多项式求导
在这个单元中,主要是让大家熟悉Java和简单理解面向对象的方法。在这一单元,主要对各层次的数据进行封装,然后重写求导函数来实现这个程序,继承在这个单元里面非常好用,很好的体现了面向对象的一些特点。当代码越来越多的情况下,如何保证自己的程序具有较好的层次化结构是非常重要的一件事,往往较好的架构能够帮助程序员写出一个非常好的程序。
在这个单元里,面向对象方法对于程序的层次化设计非常重要,一方面对象将数据层次化了,每个层次管理自己层次的数据和对象,这样使得每个层次的工作变得简单,将一个复杂的大问题分解成多个简单的小问题,然后将多个层次放到一起,就可以解决一个很大的问题;另一方面,当问题发生了变化,变得复杂的时候,在不改变架构的情况下,我们可以通过修改很少的一部分代码,来实现功能。不过在第三次作业中,由于之前没有考虑到递归的问题,还有乘法的加入,所以架构上会有些不那么优雅。
多线程电梯
这个单元加入了多线程,主要要解决的问题是如何降低各个类的耦合、各个线程的协同以及如何正确退出的问题,尽可能避免各个线程排队等待某一资源或者某线程没有被唤醒的情况出现,很类似生产者消费者模型。
在这个单元中我使用了两种架构,一种是调度器控制电梯运动,这个电梯部分实现起来比较简单,但是调度器会比较麻烦;另一种是调度器分配请求,然后电梯自己决定如何完成,这个架构相对比较简单,电梯和调度器的耦合度也有所降低,但是在性能优化上会比较麻烦。
这个单元的作业是递进的,而且架构上需要改变的东西不是很大。这个单元中提出了SOLID原则,在拓展功能那部分没有太好遵守,其他的都还好,对于面向对象方法有了更深的理解。
图(JML)
这一单元的大体架构已经给出了,主要是如何去实现满足要求的代码,一方面是正确性上,另一方面是在复杂度的控制上。这一单元对于复杂度的要求比较高,需要对得到的结果进行存储以避免重复计算,还有对计算算法的要求,以及HashMap和数组的使用。
这一单元的问题背景是图算法,而且每一次作业都是在前一次作业的基础上增加一些东西,所以我使用了继承的方法来实现来处理这些作业,对应作业的增量。同时在这次作业中了解到了“数据”和“处理”分开的好处,这样可以使得一些“处理”方式可以重复利用,减少代码的重复。
UML解析
这一单元感觉和第三单元很类似,只不过需要我们自己去设计架构。在架构设计上,我是模仿了UML的组织形式,这样解析会比较方便,而且在层次上比较清晰,便于层次化处理,当问题被分解为多个简单的小问题时,代码的就很简单了。
四个单元的测试理解和实践
- 第一单元自己写了测试数据生成器和测试机。粒度很粗,主要是进行压力测试,正确性测试很少,当出现bug时,很难排查是哪里的代码导致的问题,所以效率很低,不过当时主要的问题还是正则匹配实践过长的问题,不过这在第三次作业中就彻底解决了,因为没法用正则了。
- 第二单元是实时输入,所以自己写了个相应的输出程序,然后用管道实现实时输入,主要是测试线程能够成功唤醒以及能否成功退出的问题,这个在测试的时候,需要精心设计样例,来覆盖所有情况。
- 第三单元使用Junit来进行细粒度的测试,实现对各个函数的各种情况进行测试,可以很好地覆盖各种情况,对于bug的查找也很简单。不过这一部分对性能有所要求,所以需要对自己的程序设计一种最坏情况的样例,进行测试,例如在该单元的第三次作业中,就彻底抛弃了Floyd算法,复杂度是真的控制不住。
- 第四单元输入比较特殊,不像之前的测试那么容易构造,所以还是使用了编写特殊的mdj文件的方法进行测试。
综上,测试可以分为三部分,非法输入、分支覆盖和压力测试。在第一单元中,需要考虑很多非法输入的问题,所以需要自己去构造可能出现的非法输入在哪里。在后面的单元中,不需要考虑非法输入的问题,但是情况会更加多样化, 需要自己去覆盖每一个分支来解决这些问题。在后面的单元中,不需要考虑非法输入的问题,但是需要考虑分支覆盖的问题,要保证自己的程序在所有情况下都可以正确运行。对于压力测试,主要是在第三单元强调,如果数据组织形式比较好,算法的复杂度也控制好,同时又做到了避免重复计算的问题,那么对于压力测试不会有太大的问题。
课程收获
大三上这门课,感觉很像是对之前的数据结构和算法课的多次应用,每次作业都需要仔细考虑整个程序的架构、数据的存储以及如何处理,当这些问题处理好了,整个程序也就知道要怎么写了。除了这些,通过这门课程我对Java和面向对象方法有了更深的理解,以对象为单元,使代码更加层次化和模块化,这样可以更好的存储和处理数据,程序结构也更加清晰,便于编写和debug。除此之外,在测试上也有了较为系统化的理解,之前写的程序基本上都不考虑非法输入,但是面向对象这门课还让我们去考虑非法输入和算法的复杂度,让我们写出更加精巧的程序。
课程建议
- 希望指导书的说明能够更加准确些。在最后一单元中,指导书的说明比较模棱两可,不知道要求是什么,比如“不重不漏”是以什么为标准,是ID还是名字,经常让人不知所措。
- 希望能够在第一次作业中就给出以后作业的变化方向,在第一单元第三次作业的时候,增加了“迭代”,立马让我的程序重构了,正则不能用,后面的求导和数据存储形式全部都需要改,没有丝毫“增量”的感觉,更像是质变。如果能够在第一次作业中说明以后会增加“迭代”这个元素,那么我们会在前两次作业中加入相应的元素,保证自己代码架构的延续性。
- 前几次上机课难度有点大,差点没写完代码,如果难度较大的情况下,时间希望能够长一点,或者降低一些难度也行。
- 感觉前三个单元的三次作业难度差距有点大,第一次和第二次难度差距不大,基本上增加点东西就好了,第二次和第三次差距就有些大,有种要重构的冲动。比如电梯那单元,可以在第二次的时候就加入多部电梯,这样第二次和第三次过度可以平缓些。