rumlee 2020-06-13
前言:一学期的愉快(痛苦)的OO课程学习结束了,OO几个单元都各有特殊,实验也各有特色,不再是单纯的敲代码(但是自己还是有几次没用成功提交实验),总的来说,收获很多,遗憾也很多。同时也感谢那些不厌其烦帮助我构思帮我debug的py们。
本单元主要理解UML中的各个元素的相互关系。构建UML的解析器,其难点在于对UmlElement的理解,以及各个图的逻辑结构。
第一次作业主要是对UML中类图进行解析,第一次作业如果明白了各个类图元素,即UmlElement的关系时,问题就非常好解决了。注意到我们一共有9个UmlElement类,其关系大祇分为以下。
(徒手画的略丑)。在这里我们需要知道先知道UmlClass&Interface才可以完成实现,继承,以及向class和interface添加attribute和operation元素,最后还得向Operation添加Pareameter元素。然后AssociantionEnd的添加需要知道对应的Association。所以本人的构造函数采用了两个大循环,第一次先得到所有的class、interface、parameter、associationEnd(后二者是因为可以先构造一个大的表,再填写上一级关系的时候分配出去)。最后把剩下的元素提取出来并分配拼接起来。
然后,对于拼接,创造了属于自己定义的几个元素。MyUmlClass:包含自己的对应的原先类,以及自己的父亲,所有的成员属性,所有方法。MyInterface实际上也与class有类似的成员。如op和association,所以创建一个公共的父类被二者继承(也方便了后面关联对端的添加,因为你对端也有可能是类或者接口)。MyUmlAssociation和MyUmlOperation,其作用也尽可能模拟现实情况。具体类图和关联情况如下:
本次作业,还需要处理重名现象,所以我在会出现的重名现象的元素中,采取了双Map搜索,一个是id2元素,一个是name2元素。前者用于取类,后者用于判断重名。本次作业中重名的处理,笔者用一个Arraylist存取重名的元素(后面想想,既然只是判断有没有重名,那我直接给一个true or false的判断不就好了)。
本次作也难点有二,一个是寻求自己关联的数量,一个是getImplementInterfaceList函数的实现。
前者,在讨论区涉取多位同学的观点,总结出,关联数就是自己框上有几个点(几个由自己发出的关联),对于父类的关联数,则是全部继承下来,也就是直接相加。所以,本人创建的用于存储对端的关联的Arraylist就有作用了,直接取其大小就是自身关联数(自关联的时候,会向这个链表加两次自己,符合要求)。
后者,采用dfs或者bfs进行搜索,便可以找到所有的实现接口(这里注意到的是一个接口可以继承其他接口,并且是多继承)。在这里顺带提醒一句,一定要标记访问过的节点,不如遇到循环继承就死循环了(强测CPUTLE的血泪教训)。
有了第一次作业的基础,第二次的实现显然就比较简单了一些。类图部分不需要额外注意什么(这里需要提一句,如果你是用attribute的parenid去定位类的话,要注意此时的attribute可能是属于顺序图的,所以可能会存在找不到的情况)。在这里,我封装的类不是很多,就是把状态图的状态机封装了,因为一个状态图里可能由多个状态机(一个状态机包括,region画布,迁移,状态,和初末两种额外状态)。以及顺序图的各个元素也封装在一起,其中,为了方便处理关与生命线内incoming消息的统计,封装了新的生命线。具体实现如下:
重名问题模仿第一次。其中难点,个人认为是getSubsequentStateCount函数的实现。但是因为作业数据上多重限定,所以考虑少了许多,复杂度自然也就少了许多。只需在存储的state中进行dfs或bfs进行搜索,特别注意finalState的处理,此外就无难点。
(想不到吧还有第三次作业)本次的一致性检查一共有8个,001为重名检查,002-4为对类和接口在继承和实现上的判断(三者有异曲同工之妙),005-8则是类图和顺序图一些基本的检查,后面四个总体难度不大,维护好相应容器,对其进行判断即可。
001的处理主要明白规则的含义。对这句话“针对类图中的类(UMLClass),其成员属性(UMLAttribute)和关联对端所连接的 UMLAssociationEnd 均不能有重名 ”的理解极其重要,尤其是后半句,本人的理解就是类的对端(也就是我在类图中存的所有的关联)其作为UmlAssociationEnd有自己对应的名字,这些名字揉进UmlAttribute的name中,其中不得重合。
002-4难点在于接口的多继承,这里就不得不使用dfs或者bfs了。三者侧重不同,如下:
002是循环继承的问题,主要考虑类的单继承和接口的多继承。这里本人图简单,并没有构造简便的算法,对于每一个类或接口判断自己是否在循环里,是就返回自己,否者null(判断遍历时有没有出现自己,同时也要防止死循环,在判断出现循环的时候就要退出)。
003是重复继承,相比之前来得简单,只需标记每一次出现的继承即可。
004是重复实现,针对的是类,但是涉及了类与类的继承,类与接口的实现,接口的多继承,看似复杂,实际上也只是003的一个提高问题。
实际上这些函数的实现都
可以回到对应的对象里创建相应的方法。所以结构上与第二次相差不多(就是新类出现多了新的关联),就不贴类图了。
第一单元,主题是多项式求导。随着一次次迭代,多项式的种类逐渐丰富,求导的形式也逐渐丰富。本单元主要是一种面向过程到面向对象的一次转变(如果用C语言来编程未必不可以实现)。但是我们注意到面向对象的编程,把每一个类变成了一个对象,在本单元中,可以把多项式看成对象,每个对象蕴含了各个多项式的特征和求导规则以及toString方法。尤其是第三次作业的实现,充分体现了面向对象的层次化,将类的共性抽象成一个新的类被继承,把求导的特性构成一个新的接口被实现。并用以这个单元学习的工厂模式。总的来说,架构上基本遵循了:处理输入——每个对象进行求导——处理输出。
OO方法的理解,在第一单元算是初次体验了面向对象的思维,即把需要完成的某些行为和属性抽象成某一个类,对类进行封装。最后在mainclass进行一个类似面向过程的操作,这个时候的mainclass往往会比较简洁明了,对于阅读者,思路会更加清晰。同时在checkstyle的影响下,对类名方法名的命名都变得清晰,以及行数的限制也让自己不自觉地简化方法,不会像实现C的时候进行长篇写代码。不过在第一单元实现过程中自己的实现不是非常好,比如层次化上,自己并没有像自己构思的那样,使得三次作业次次重构(泪不禁流了下来);并且在化简多项式的时候,自己也没有过多的关心,使得自己强测都没问题,但是性能分直接没了。
第二单元,主题是多线程电梯。本单元出现了面向对象的一大特色,多线程的处理。本单元关键就是多电梯和调度器的协调。同时,因为学习了生产者消费者模式,所以整体的架构还是比较清晰的,可以写一个生产者进程(处理输入),把调度器当成托盘,多电梯成为消费者。之后考虑的就是,生产者和消费者直接的冲突和多个消费者之间的冲突(同步互斥问题)。
本单元对OO的方法理解上便是多维思维的突破,从单线程运行成为多线程运行,此时就更有面向对象的感觉,因为只有多个对象同时进行某些事,就成为了一种类似多线程的思维。所以本单元在对对象的封装来说也比上一个单元来的更完善。同时在迭代的处理上,此单元的作业,都是在不断丰富上述三个对象,所以总体上,实现了较好的重构。不过自己在实现上有些小遗憾,调度器的作用似乎只是一个托盘而已,对请求的分配并没有实现,而是让电梯自己去抢请求(性能分炸裂警告),所以强测依旧没有出现错误,但是性能较差。
第三单元,主题是构建一个社交网络。本单元是对JML语言的理解上的进行的,所以整体架构上是课程组已经给出的,而我们的主要就是根据对JML的理解进行代码的编写,在这个过程中体会规格化的设计。
OO方法的理解,在本单元,让我充分理解了一个架构的重要性。因为JML的存在,架构师可以给出自己架构设计而成的JML文件,而实现的人却可以有不同的算法。架构作为一个项目的主体便在其中体现了出来其重要性。同时,本单元的学习中,也让我重新学习了图论,也明白了,架构的设计之下,也要考虑对应的数据结构的设计,自己一开始也是无脑构造了几个很普通的图,到了第三次的时候才发现居然有一个叫做并查集的好东西,直接让自己之前实现的方法复杂度下降了许多。所以一个好的架构加一个好的数据结构是不可缺少的。
第四单元,主题是UML解析器。本单元是根据对UML各个元素的理解进行编写的。其最上层结构通过官方接口给出,但是架构还是得自己去实现。而第四单元可以说是前三个单元的集大成者,有第一单元的层次化思维,第二单元勉强有一个顺序图可以让我理解第四单元顺序图是啥,第三单元则是规格化的设计和自己理解上的一个好的数据结构(比如这次我就用了双map来处理重名问题)。总体架构已在上文提及,不在多说。
本单元对OO的方法便算是一个OO思维的成熟。自己本次的代码,无论是层次化,还是为了提高内聚程度封装了原来的类。都是面向对象思维的一种提高。而在开始写代码之前也是先把各个元素关心捋清楚了,知道各个元素间的关系构建合适的架构,才开始编写代码,而不像之前的拿到题目就开始莽,虽然构思构了许久,但不得不承认,有了一个明了的架构,在代码实现上效率大大提高了。
第一单元无论是自己测试还是互测,都是自己手动捏造数据(也不得不说,第一次作业的数据还是比较好捏造的)。自己捏造数据的思路也比较清晰,先是随机捏一些数据,这个时候就学习了以下自动生成数据的方法(但是自己没有学习python,没能成功自己写一个自动评测机,虽然就算会写py可能也还是不会很好的写好了脚本)。再者便是自己根据自己对作业的理解构造一些特殊的数据,比如测试效率的多个sin函数嵌套。或者第一次作业的时候,对系数和指数赋予0的不同操作。在自己测试的时候也充分使用了debug功能,对自己程序的思路,也写了几个刁钻的数据,也成功发现自己的错误。
第二单元引入了多线程,同时时间戳上的处理,让自己写测试数据变得困难。自己的测试策略大体上还是不变,先是随便生成数据,接着就是自己通过时间上的计算,设置了几个容易发生死锁或者互斥处理不当的数据。这些数据也帮自己发现了很多错误。同时在在本单元上也使用了JProfiler进行死锁的寻找,最后发现,因为自己使用了让电梯自己去抢请求的策略,并且对电梯类的大部分方法进行加锁操作,所以没有出现死锁。
JML单元可以说是测试的一个新转折。知道JunitNG可以根据自动测试数据,自己也尝试了测试。不过这种自动化测试,效果上其实不如我们之前写的那些随机生成数据的自动评测。其选择的数据基本上是非常边界的数据,这里的边界几乎就算int类使用两个最值,对象类使用null。但是在实验的时候也知道了Junit测试,可以自己写一些测试数据(类似自己构造数据),这种测试,实际上比自己写的数据来的稍微简单,对于类属性可以自己设置,然后对指定方法的正确性,覆盖率进行测试,总体上也是很不错的。不过说句实话,个人还是偏向自己构造数据,手写测试集(当然,如果数据好构造,我也会尝试用自动生成器)。
第四单元的测试,基本也可以使用上述几种方法,但是也值得一提,自己构造数据的难度有点大(不像之前自己写一些txt就好,而得自己去starUm画图然后再导出,所以测试效率不是很高,不过好在一个图可以构造很多子图去满足不同需求,一定程度上提高了编写效率)。自己也算是构造了一个循环继承的例子,作为特殊例子对自己程序有效性的验证。不过也许是没有互测的存在,自己对特殊数据构造也不是很热情,也是找了一些自动对拍器和大佬们对拍来查自己的bug。
本学期学习的面向对象和上学期学习的计组配套的两个语言(HDL和MIPS汇编)相比,OO的学习更加系统和完善,自己能力的提升也比较高。同时再与大一学年学习的C语言再次对比,OO是一个新的思维方法,不同与面向过程的编写思维,OO给我能力最大的提升便是抽象思维,可以把一些事物抽象出对应的行为和属性进行封装。抽象出不同类的共性,构造其中的层次化关系。另一方面,因为课程组严格对代码风格的要求,使得自己无论是代码间距还是代码命名都变得规范了起来,并且一周上百行的代码编写,不仅提高了自己代码的能力,同时,也磨练了自己长时间思考,耐心敲代码的心(一杯水,一台电脑,一份指导书,一次作业敲一天)。
在OO学习中,不仅学习了java语言,其中还学习了其他许多东西,比如JML语言,starUML的使用,JVM的概念理解。以及几次实验也让我学到了许多编程模式和一些相关的JVM垃圾回收机制。(作业还回顾了求导知识,自己还对着自己的数据求导求了半天;个人社交系统也让我明白钱是借出去拿不回来的)也在最后两单元中让我重温了图论知识,收到了来着算法的毒打温暖。
同时,也不得不将OO课程和OS课程进行对比。两门课程有一定工程化设计。OS7次Lab迭代,最后实现了一个简单的操作系统。OO四个单元更像是甲方给我四次要求,我去实现一样。让我体会到了软件开发的感觉,自己编写代码的时候,从构思到算法的实现,到自己修bug和强测之后自己修bug,用自己语言描述自己的bug到互测的时候,用一定的策略去测别人的代码。这些过程就像一个人体验了架构师,算法实现师,测试组等几人的工作。工程化思维,可以是本次课程的一大收获。
最后呢,一个OO课程,让我在应付课程的过程中,渐渐变得认真了起来,尤其是最后几次作业,自己花在OO课程上时间也逐渐变多了。这个过程中,因为自己的上心,也培养了自己查阅相关文献的能力,比如在tarjan算法中,自己就查阅了许多博客。我们计算机专业,未来面对的是很多未知,所以懂得用网络资源去学习,是很重要的,不怕不会就怕自己愿意去学习。
虽然每次理论课老师都会给出一定的讨论题来讨论。但是个人感觉理论课上很多知识并没有涉及,更多的是一些面向对象的思维,而一些与作业相关的内容并没有给出。更希望的老师们能与作业相结合,通过作业来体现本章的一些OO思想。尤其是一些面向过程编写方法,自己大部分是通过网上学习完成的,理论课上单纯通过一些概念来讲述面向对象思维效果不如我们在实现作业时慢慢悟到。周围也有很多同学并没有认真观看视频,观看理论课的认真程度还不如观看实验讲解视频。并且,理论课上的内容有时会滞后很多于作业,尤其是在第二单元时,在讲解互斥管理的时候,似乎在讲完互斥锁的时候,就会涉及到死锁的某些问题(第一二次作业的时候),而理论课在第三次才提及死锁。整个第二单元的理论课,也就第一次有印象,其他几节好似没有发生一样。
课程组在实验后不给予每一次的实验相对标准的答案,有自己的考量,这一点我们可以理解。同时,也许代码类的实现各有不同,标准答案可能不好给出,但是希望课程组能给出大部分出错的原因(实现上可能会比较麻烦课程组),而不是给出得分情况,我自己都不太清楚自己得分多少,观看得分情况意义何在,数据统计意义合在。所以还是希望能给出一些实验反馈,比如易错点(如OS会给出每次课下的一些易错点),没有实际意义的反馈,实验训练效果会有一定的下降。
我们的作业对于算法的要求实际上已经下降了许多。提出这个意见也不是抱怨我们算法难,而是更希望对于下一届的更高的考虑面向对象思维的培养。比如第一单元的时候,我们优化占了20%,这算是一定的算法问题,但是这种优化和第二单元20%性能优化差别很大。我比较赞同第二次性能可以占这么多分,同时也希望能提高这一占比,因为多线程的性能分更多的是调度上的考虑,更多的是多个线程的交互,更有面向对象特色,而第一单元的优化,更多的是去利用一些特殊性去化简,和面向对象的关联不大,这20%从培养能力上是值得的,但是从培养面向对象能力,20%占比不值得。此外我想的就是,如果能根据代码的“高内聚低耦合”特性来判定“性能”(实现可能会很难),这样子,学生才能更多从面向对象来想办法降低耦合性,这样更有面向对象思维能力。
(以上语言可能有些偏激,但绝对没有任何恶意,课程组可以听一听意见,至于需不需听取,如何解决,取决于课程组本身)