OO总结

OO总结

一、第四单元三次作业的架构设计

看到了UML解析器整体的需求,我发现似乎看起来是树结构可以解决的需求,不过我并没有执着于把树单独整理成类,从本次迭代开发的过程来看,Class、Interface等具有各自不同的特性,尤其是Class依据约定只会有一个father,而Interface可以有多个,还是将以前学到的算法内化于本次的设计,针对每种不同的元素构建不同的存储与索引方式比较好。

总体思路是对不同信息需求层次的元素进行分批处理,对于可能存在重名问题的元素建立name到Interger(num)的Map,如果是不存在重名的元素则放入1,如果重名则放入2。对每一个接口中定义的方法逐级下方并在每一次调用每个自建类的某一个方法的时候进行缓存。

(一)第一次作业

很直接的想法,基本没有很多设计的痕迹。建立MyUmlClass、MyUmlInterface、MyUmlInterface类用来做信息存储和指令需求处理(主要是递归及缓存的需求)首先是在构造器中进行解析,逐个遍历elements数组,第一批解析UML_CLASS和UML_INTERFACE,如果不是这两种就放到一个ArrayList中作为第二批再分析。第二批处理UML_ATTRIBUTE,UML_OPERATION,UML_ASSOCIATION,UML_GENERALIZATION,UML_INTERFACE_REALIZATION。如果不属于这些类则放到第三批进行处理,第三批包括UML_PARAMETER和UML_ASSOCIATION_END。这样的处理层次是考虑到后面将如Operation和Attribute直接在解析时放入具体的类的想法。因此先解析了class和interface,这两个是第一层的,第二层则是或属于第一层的一部分,或者需要第一层建立好的信息。而第三层的parameter和association_end则是建立在第二层operation和association的基础上。每一层对象建立时若有下层对象的归属需求则建立id到具体实例的map,第一次作业指令通过class的name来找到元素,因此也要建立name到实例对象的map。
在每一个具体的指令执行时,首先查找name到class的map查看是否存在,然后查看className到num的map查看是否存在重名,这两步如果有异常要抛出。如果正常找到了class,则调用该实例的相应方法(只有getClassOperationCount方法还加了一层位运算的指令类型初步处理)。
具体的自建类的具体方法,唯一能称得上需要设计的点就是涉及其父类的信息的时候需要通过递归地调用其父类的同一个方法,在这里做了缓存设计,当继承链条为A->B->C->D时,我如果已经调用了B的方法,则A调用方法时只需要递归到B这一层即可获取B已经缓存好的结果。这个设计充分体现在各个方法中,最典型的是getImplementInterfaceList方法(在自建类中直接调用的是getImplementInterfaceNameList方法)首先获取的是名字List,那么要先找到这个类实现了哪些接口,包括这个类自身直接实现了哪些接口,这个类所实现的接口继承的所有接口,以及这个类的父类实现的所有接口。直接实现的接口通过在解析时分层储存的信息直接可获得;这个类所实现接口的继承的接口则通过调用MyInterface类里的这个方法获取,而MyInterface类里这个方法也用了缓存+递归(每个接口直接继承的接口(直接获取)+递归获得这些“爸爸”接口所继承的接口);这个类的父类所继承的接口则递归调用其父类(若存在)的该方法获取。

(二)第二次作业

第二次作业实质上就是加了两种图,状态图和时序图。整体处理思路和处理方法与类图并没有显著的区别。为了不使主处理类过于臃肿,我将三种图的元素和结构交给三个类来处理,每一个类的map的设置和方法的实现均类似于第一次作业的主类。
如果说第二次作业和第一次作业真正的实现的区别,只有两个地方。
第一,因为三种图没有直接的结构联系和沟通的需求,因此我建立了MyUmlClassModelInteraction、MyUmlStateChartInteraction、MyUmlCollaborationInteraction三个类来几乎全权地分别处理类图、状态图和时序图。解析时只是基本地做层次分类,然后直接交给相应类进行处理,具体指令的方法也是直接调用对应类的方法。MyUmlGeneralInteraction几乎完全只是任务分派与中转的作用。每一个真正做任务处理的类则和第一次作业的模式如出一辙。不再赘述。
第二,在state的寻找后继状态的过程中,我意识到不能简单地去递归实现,因为当状态成环(有向图成环)会直接进入死循环无法跳出,因此getSubsequentStateCount方法,我采用了bfs来解决需求,可能会有一定的重复,但综合评估,考虑到不是很大的数据量,为递归作复杂的的标记改进不如直接改用bfs,保持一定性能的前提下不容易出错。

(三)第三次作业

由于第二次作业我已经尽力去清晰地简化处理流程,因此第三次作业在没有增加新的图和元素种类的情况下只是检查的工作。1-6的检查针对类图,7-8针对状态图。1十分简单,遍历我们之前几次作业已经准备好的map即可,5、6在解析时各自加set集合并设flag即可。7、8也是。相对需要处理的是2、3、4,而且考虑到要按顺序抛出异常(2、3同时先抛2等)。一开始检查循环继承时我用了递归处理,结果类与类的继承关系中发现面对一个D->A->B->C->A的情况会陷入死循环,因此做了一个visitSet来在上述情况中跳出(类这边的循环继承检查因为类就一个“爸爸”,就顺着找就可以)。而接口方面相对复杂,我采用了如下方案:对每一个类图中的interface用bfs的方法直接检查它能否通过某一个继承链条继承到自身,如果可以就说明是循环继承的一个节点。对每一个interface均遍历即可获得所有的处在循环继承链条上的interface。在排除这种情况后,我的每个类或接口不能重复继承另一个类或接口的设计就相对巧妙。在缘由的找实现接口的方法中进行微调,在每一层记录直接实现的接口与间接实现的接口的不去重情况下的总数并存储下来向递归前一层返回。同时原有的“找爸爸”行动不变,(原有方案采用set存储,用addAll方法自动去重)。每一个interface遍历一遍,如果存在自己所存储的实现接口的Set集合的数量与通过递归返回的累加计数不一致,就说明存在重复继承。由于约定了类不会多继承,因此不需要检查类。
而对于一个类不能重复实现同一个接口则是在上一个检查的基础上增加了一层一个类可以继承多个接口的关系。采取同样的方案,对每个类调用相应方法即可。(通过实现接口的Set集合数量与递归返回的实现接口的总数量的对比确定)。

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

搬运一下我前几个博客做的架构设计的分析:

一单元:

第一次:
只有简单的几个类,Poly类用于解析字符串,ComputeExp用于计算和化简,PowerItem为幂函数项的存取与与解析类,DealStr为字符串预处理类。

第二次:
BasicUnit为基本求导单元,(最核心的部分)其中存储了三个函数类的实例,提供了求导方法。Collect类提供解析字符串的方法,Derivative提供求导连接及化简的方法。

第三次:
大体上分三个层次获取表达式的结构,每一层有toString方法和求导方法。
表达式层(由基本单元构成,toString方法支持表达式因子层次输出)
基本单元层(是表达式层里面通过加减运算符连接的各个单元,即用乘号连接的若干项)
函数因子层(三角函数等)
意图是:最终效果为对整体字符串直接构造为表达式层,调用其求导方法返回结果。表达式层的求导结果都直接由构成它的若干基本单元的表达结果直接相加,基本单元层求导结果是其若干个相乘项对每一项遍历求导再相连,因子的求导则分为表达式求导,函数求导等,表达式因子的求导直接调用表达式层对应类的求导方法即可,函数求导则像之前一样由诸如正弦函数类、余弦函数类来完成。

二单元:

第一次作业,程序运作过程如下:由GetRequest类获取请求,将请求压入传送带的待处理队列,并唤醒tray的弹出请求方法,给调度器传递消息(通过调度线程类的方法调用),调度器将请求加入电梯线程的等待list,并通过Monitor实例唤醒电梯,于是电梯开始运作。这样的模式不断地运行,电梯在开关门时会实践捎带策略。第一次作业中我使用LOOK算法,电梯停滞的时候随时等待主请求的进入。当主请求进入时,马上去接主请求并在路上不接其他人,接到主请求也就确定了主运动方向,只有出发楼层为当前楼层且方向与当前的方向相同的时候才会捎带该请求。到这一趟需要到达的最边缘楼层时就接下一个主请求。在开关门的时候都实践捎带的策略。
第二次作业在第一次的基础上增加了电梯数量,扩充了电梯课停靠楼层,我的程序架构完全没变,只在原有基础上稍作了扩充并且在选择电梯的时候稍微判断了一下运行方向和当前楼层等信息。第二次电梯的限定人数通过调度器处调用相关方法来判断电梯内人数及电梯等待队列人数,两者综合不超过最大人数。即电梯线程中不需要考虑超载问题。
第三次作业由于性能指标多增加且只增加了每个乘客的等待时间,于是我不得不重新写了我的捎带策略,即一方面,首先保证如果出发楼层为当前楼层,则在不超载的情况下必然先进来,不管是否同向,另一方面,电梯的运行方向达到的最大楼层由电梯内的正在执行请求的最大或最小楼层决定。调度方面其实算法反而简化了,由于动态过程相当难以预测与分类,于是我采用了只看waitList多少。于此同时,电梯的超载控制转交给电梯自己来完成。即可能电梯满载时waitList是有剩余的(第二次作业则不会出现此情况,为较大的修改)。同时因为电梯读入结束时如何停止电梯线程的问题,我增加了Dispatcher类专门负责电梯换乘和电梯运作的停止信号的产生,此处的与其他若干线程的耦合度相当高。

第三单元:

第一次:
整体的实现与JML的形式没有特别大的区别,除了我将JML中的每一个静态数组都换成了一个HashMap,一个ArrayList,有的甚至还有HashSet,大量冗余,这种毫不吝惜空间的做法为第二次作业带来了麻烦。isCircle方法是稍微需要设计一点的方法,我采用了bfs(后面作业联通相关除了并查集我基本都用的是bfs)。其余的都是简单的照着JML实现。

第二次:
改成了类似缓存机制,每加一个关系就将和Age总和,value总和等相关的更新一遍,避免被TLE。总的架构就是基于JML进行方法的扩充,没有很大的改动。

第三次:
在第二次作业基础上主要就是多加了qmp和qsl为首的依赖图的相关算法的方法。因此我增加了一个专门为qsl准备的类,connection,在这里专门进行相对麻烦的暴力bfs处理,包括检查id1和id2之间是否连通并预处理的bfs。还有qbs的并查集实现我也专门去查了资料,我没有单开一个类,但是在MyNetwork里加入了一个方法findFather来辅助实现并查集的优化。

可以看到第一二单元是最重要的是基于需求充分实现,第一单元整体就是基于分层建模的思想逐步对求导进行实现。在第一单元第三次的开发中这里我明白了一件重要的事情:当一个程序需要分很多层进行处理时,需要对每一层和其上与下面的层次的“接口”做好,就是我实现什么功能到哪一步,这样在需求调用时可以提供清晰地逻辑。

第三单元基本就是按照JML去实现,主要是阅读功能,设计架构,选择算法并实现。

第四单元我认为主要工作是理解与摸索,程序的结构某种程度上和UML图的结构是对应的,需求的分离也意味着代码交互空间的分离。

三、四个单元测试理解与实践的演进

在第一单元中,我们很容易能构建出随机数据,也同样能够构建针对性的数据进行相对充分的测试。在这一单元,我对测试的各种方法了解的尚不充分。因此以自行构建有针对性的数据为主要测试形式,这就让我在第三次作业中遇到了一种盲点情况没有测到被扣了一定分数。

第二单元,大量生成数据是一种方式,但可能在debug时这不是一种体验很好地方式,对于多线程的不可复现的bug,能出现一次就不容易,更何况随机生成数据有着随机性、数据量相对较大等,而且每个人的电梯运行效果不同,因此我构建了专门的检查器来检测电梯运行是否合理,数据方面则采用如下策略,随机数据发现问题,缩小数据范围和数据规模以便能更容易复现bug及精确定位问题。

第三单元,第一次作业我认为过于简单,没有怎么做测试结果出了问题,第二次作业我做了充分的功能性测试,数据随机生成,强度和随机性可能比强测还要略强,但是很可以因为采用了6000大数值初始化HashMap大小导致在课程组的评测机上爆出堆溢出的异常,而在本地完全没跑出来问题,经过询问与推测发现是JVM的堆大小参数设置问题。本地通常是单机单次运行。这种bug一定程度上不属于数据上能测出的问题,而是经验问题。第三次作业也做足了功能测试但是没想到CTLE卡的那么紧,本地测得的CPU时间也和评测机上的不一样,只能是通过算法层面去重新选择。

总的来说,几个单元下来,我对于写脚本进行批处理、随机生成等等有了一定的体验与经验,但是很多bug不是这样的测试能发现的,需要各种经验的积累和长期的磨练。

四、课程收获

首先基本入门了java语言,对于面向对象有了一定的认识,感叹于各种封装的巧妙。在第一单元的收获主要是工程实践上的,对于不同层次的方法调用效果一定要有明确定义才能够顺利地进行层次化建模。第二单元主要是面向对象思想的感知,初探多线程,终于知道了什么是真正玄学的bug,不可复现的bug的调试难度要远大于能够复现的bug的难度,对于对象管理有了前所未有的认识,对线程调度也是有了直观地体验。第三单元就是我个人的各种体验,包括基本测试的重要性、对JVM参数需要有了解、对卡算法的情况下一定要选择相对较优的算法的意识。这个部分我在第三单元博客已经进行了充分地思考。详见OO第三单元作业总结。第四单元学到了UML,学会了递归缓存,理解了各个UML图的基本结构。

不论从理论上还是实践上,OO课程都给予了充分地体验面向对象进行程序设计的体验。

五、三个具体的改进建议

1、评测机的搭建作为官方教程的一部分(哪怕是每个单元结束给出也可)而不只是作为研讨课内容和理论测试方法上的探讨。
2、建议给出CPU时间测试的接口,本地时间通常不一致。建议给出JVM参数,并且提醒同学可能在相应参数设置上出现本地与官方评测机不一致的情况。
3、建议不同班级的研讨课分享内容及成果进行共享,不论是PPT形式还是博客总结之类。

六、在线学习体会

个人认为除了第三单元project除了很大的问题之外,其他的与在学校进行学习的效果应该基本一致。最主要的project有明确时间计划push,核心环节不依赖面对面教学。我认为研讨课非常适合线上形式。每个同学都能清晰地看到PPT内容,每个同学都能互不干扰地获取自己所需要的信息。

总的来说,OO是一门真的让我学到东西的课程。

posted @ 2020-06-18 01:40  BettyRay  阅读(125)  评论(1编辑  收藏  举报