2022北航面向对象第四次作业分享及学期总结
2022北航面向对象第四次作业分享及学期总结
第四次作业分享
本次作业由三次子任务组成,从5.30号开始,经过三次的迭代开发,实现了一个UML解析器,支持对传入的UML模型进行分析,并且提供相关查询的接口。
UML图
UML(Unified Modeling Language),又称标准建模语言。是用来对软件密集系统进行可视化建模)的一种语言。UML的定义包括UML语义和UML表示法两个元素。
-
类图
围绕一个具体主题,展示相关的类、接口,它们之间的关系(依赖dependency、继承generalization、关联association、实现 realization),以及必要的注释说明。 -
顺序图
顺序图来自于通信领域,表示通信实体之间的通信关系。
-
状态图
状态图描述了对象的状态化存在和变化。
开发过程
本次项目开发基于课程组已经提供的程序主干逻辑(实现将传入的UML模型解析为独立的UmlElement提供给分析器),并根据提供的相关接口设计自己的UML分析器。
整个开发过程分为三次迭代,依次为:
-
实现类图的构造和查询
-
实现顺序图和状态图的构造和查询
-
实现模型的自检功能
架构设计
本次作业设计器需要从UML模型解析器提供的Uml元素中提取相应关系并进行记录,为指令查询提供必要信息。显然,解析器提供的UmlElements是独立的,但是其中包含了指示他们与其他元素之间关系的变量(parentId, represent等等),因此我本次采用了封装的思想进行建模,先根据底层逻辑封装自己的元素类,并进行建模。等到指令查询时,只需要针对现有的模型进行分析。
顶层类的调用关系为
(MyImplementation --> MyPackage(打包) --> MyModeler(建模))
第一次开发
本次开发建模关注点在于继承(类的单继承和接口的多继承)和接口实现。本次需要实现的接口方法比较简单,合理建模之后只需要进行简单的静态查询,这里不展开阐述了。
封装类的成员变量列表⬇️
public class MyClass extends MyElement {
private Visibility visibility;
private HashMap<String, MyOperation> operations;
//id---operation
private HashMap<String, ArrayList<MyOperation>> sameNameOperations;
//name---List<operation>
private HashMap<String, MyAttribute> attributes;
//id---attribute
private MyClass father;
private HashMap<String, MyClass> sons;
//id---son
private HashMap<String, MyInterface> interfaces;
//name---interface
private HashMap<String, Integer> map;//referenceId--cnt
private int subClassCount;//直接继承此类的子类数量
private int operationCount;//操作数量
}
封装接口的成员变量列表⬇️
public class MyInterface extends MyElement {
private Visibility visibility;
private HashMap<String, MyAttribute> attributes;
private HashMap<String, MyClass> realizations;
//id--realizations
private HashMap<String, MyInterface> fathers;
private HashMap<String, MyInterface> sons;
}
第二次开发
本次开发建模关注点在于顺序图和状态图的模型构造,其中针对状态图中的状态提出了关键状态这一概念,这里应该注意自然语言表述的模糊性,这里也属实给包括我在内的许多同学带来了不小的困惑。
在这里我才用了多次dfs查找路径的暴力算法,没有过多重视这里的性能问题。
判断关键路径对应的代码⬇️
public boolean getStateIsCriticalPoint(String stateMachineName, String stateName)
throws StateDuplicatedException, StateNotFoundException {
if (dupNameOfState.contains(stateName)) {
throw new StateDuplicatedException(stateMachineName, stateName);
} else if (!nameMapOfState.containsKey(stateName)) {
throw new StateNotFoundException(stateMachineName, stateName);
} else {
if (finalStates.isEmpty() || stateName == null) {
return false;
} else {
boolean tag = false;
Iterator<Map.Entry<String, MyFinalState>> iterator1 =
finalStates.entrySet().iterator();
while (iterator1.hasNext()) {
MyFinalState finalState = iterator1.next().getValue();
//状态机模型本来就无法从 Initial State 到达任意一个 Final State
tempMap.replaceAll((key, value) -> false);
if (hasPath(tempMap, pseudostate, finalState, null)) {
tag = true;//原本initstate可以到达其中一个finalstate
break;
}
}
if (!tag) {
//本来就无法从 Initial State 到达任意一个 Final State,则所有状态都不是关键状态
return false;
}
Iterator<Map.Entry<String, MyFinalState>> iterator2 =
finalStates.entrySet().iterator();
while (iterator2.hasNext()) {
MyFinalState myFinalState = iterator2.next().getValue();
//删除某个状态之后无法从 Initial State 到达任意一个 Final State
tempMap.replaceAll((key, value) -> false);
if (hasPath(tempMap, pseudostate, myFinalState,
nameMapOfState.get(stateName))) {
return false;
}
}
return true;
}
}
}
public boolean hasPath(HashMap<MyStateElement, Boolean> map, MyStateElement start,
MyFinalState end, MyState stateExcluded) {
map.replace(start, true);
if (start == end) {
return true;
} else {
for (MyStateElement linkedState : start.getLinkedStates()) {
if (!map.get(linkedState) && linkedState != stateExcluded) {
if (hasPath(map, linkedState, end, stateExcluded)) {
return true;
}
map.replace(linkedState, false);
}
}
return false;
}
}
第三次开发
本次迭代为分析器增加了规范性检测功能,其中重点在于针对重复继承和循环继承的检查。在这里鉴于规范性检查只会被调用一次,因此不存在性能问题,我仍然采用了dfs的暴力检索算法,其中需要注意标志位的更新。
本次作业感受
本次作业的代码实现部分其实不难,主要难点在于边界的考虑以及指导书的理解。我认为本次实验指导书提供的不够完善,这样就是导致不同人对本次实验要求的理解层次不一样,并且会出现过分解读,导致花费很多的时间在虚空debug上。
但我认为这单元的作业能够帮助我们更好的理解面向对象的思想,在建模的过程中体会层次化的编程理念。
本学期课程总结
在这学期面向对象课程开始之前,课程组曾经在寒假给我们提供了java的预习,但由于本人假期太过懒惰,假期没有仔细完成提供的预习任务,导致在开课第一周我还在通过mooc学习java的基本语法。在开课前我意外读到了知乎上对于面向对象课程的批评,并且我在完成第一次作业时遇到了极大的苦难,所以我刚开始对这个课程的恐惧度还是比较高的。但是在逐渐熟悉了java语法以及面向对象的编程思想后,在之后的单元作业中也逐渐轻车熟路,逐渐积累起了自己的经验。总体而言,我对本学期的oo课程满意度较高,并且在学习过程之中比较愉快,也学习到了很多的编程技巧和知识。
本学期的课程一共由四个单元组成:
-
第一单元: 表达式的建模,多项式括号展开以及同类项合并
-
第二单元: 多线程程序设计,实时电梯系统的模拟
-
第三单元:面对JML编程,实现社交网络查询系统
-
第四单元:UML规则认识,实现UML图解析器
我个人认为这四个单元的难度排序为
1 > 2 > 4 > 3
这四个单元各自的难点也并不相同
对第一单元来说,作为第一次作业,我个人对于java和面向对象编程并不熟悉,并且表达式的解析所需要的思维量也比较大,所以做起来有点力不从心;
而第二单元的电梯作业,由于是我们第一次接触多线程编程,所以在学习和熟悉的过程中出现了很多的bug,并且需要考虑电梯的最优调度策略,也劳费心神;
第三单元的社交网络查询系统,更多考验了我们的图论算法能力,并且第三单元的作业对于代码性能进行了梯度测试,所以我们不仅要完成一份正确的代码,同时也要保证程序性能的优异,在一部分,我们也是绞尽脑汁;
而最后一个单元的任务,一部分困难在于对UML规则的彻底理解,另一部分困难在于要充分理解指导书的要求。
架构设计和面向对象思维的进步
现在回看第一单元的表达式解析任务,属实觉得这是对我帮助很大的一次项目,另外再看自己当时写的代码,也不免发现自己有很多设计还有待改进。
从架构设计来看,我第一次项目主要采取了工厂化的设计模式,同时对表达式的层层结构进行了细分,实现了彼此之间的泛化和实现,同时我也广泛采取了哈希表的存储结构,实现了快速的检索。总体来看,通过我一次的打磨修改,我第一次的项目整体冗余、内聚和耦合都控制的还不错,当然我第一次实现的工厂模式还比较浮于表面,并没有真正体现工厂模式的精髓,这一点我认为我还有待提高。
第二单元的电梯作业,我广泛采用了生产者消费者模式,为了避免轮询,采取了沉睡唤醒方式,同时采取了信号量的机制实现线程间的通信,避免了死锁。在这次作业中,我在临界区的划分上花了很多的功夫,往往总是划分的不够细致导致出现资源并未实现互斥,同时线程安全的考虑不够充分,比如在刚开始我的输出部分就并没有保证线程安全,导致出现bug。
整体架构来看,我第二单元相较于第一单元的内聚性做的更好,我对一些基本的数据结构都实现了类封装,使得我的很多基本功能都被封装在一个类中,这样也更方便我对资源实现更好细化的互斥。同时,我横向电梯与纵向电梯之间的耦合度控制的也较低,只彼此提供了少数的接口操作,主要是通过对队列的互斥访问进行数据通信。我认为本次作业有一个最大的地方需要改进的就是我并没有实现利用工厂模式完成电梯不同调度策略的装配,这也导致我的代码可能复用性会较低。
第三单元的主要架构大体课程组都已经给我们设计好了,我们只需要填充相应的接口。通过这一单元的学习,我充分认识到了代码性能优化的普遍方法,我之前一直对性能的优化没有概念,而在这部分的代码完成中,我广泛采用了添加时记录,静态查询的模式,这样充分简化了我的算法复杂度,使我的项目性能得到了充分的提高。同时,在学习过程中,我也对并查集、狄杰斯特拉等经典的算法再学习,感受到了算法之美。
第四单元的作业我认为是对我们一学期面向对象思维的统一考验。对uml的建模过程中,我们就需要对不同层次的结构进行类封装,同时也要实现许多复杂的类关系。这充分展现了我们的面向对象思维。在这部分任务,我对每一个底层逻辑都实现了类的封装,并且对方法实现了细化,所以我认为我这一次项目的面向对象化设计还是不错的。
单元分数和Bug
-
第一单元
- 98.4525——0/32
忽略了x**2可以改写为x*x
- 82.8676——3/20
代码风格分直接摆烂了,只得了6分,性能分放弃了,强测有八个点80分出头(原因是因为当时这次作业做的十分挣扎,到截止前勉强将全部bug改完,并且几次尝试优化代码都导致原本正确的bug复现,所以最后为了保险直接没管)
- 91.6784——0/30
相比前一次作业,实现了不考虑三角公式以外的公式最简化,性能仍未达到最好。
-
第二单元
- 50.9774——6/21
调度策略出现漏洞,许多测试点超市,同时输出线程安全未保证
- 98.7143——0/34
调度策略未达到最优
- 93.2602——0/20
调度策略出现了盲区,没有考虑到横向电梯之间也可以换乘
-
第三单元
- 90——0/7
qgsv方法中复杂度达到了n**2,导致cpu超时
-
100——0/17
-
90——1/16
狄杰斯特拉算法中多加了一次无效遍历,导致复杂度膨胀
-
第四单元
-
100
-
100
-
100
-
通过对本学期每次作业的分数分析,可以看到第一单元和第二单元遇到了较多的困难,其中由于第二单元第一次作业的完成期间一直在准备校歌赛,所以没有用到足够的心思,导致这次作业出现严重的崩盘。另外,我对性能分的固执度较低,一部分是我自己的原因,另一部分是由于自己也没有把几乎所有的空闲时间都花费在oo上;另外,在互测模块,我对防御度还是比较不错的,但是我的攻击性却没有始终很强盛,一直秉承着自己没被hack就不hack别人的和平理念。
测试部分
我的测试模式主要采用黑盒测试 + 对拍,刚开始对测试流程处于摸索状态,逐渐学会了打包java程序,并且利用命令行运行,同时通过大随机来增加数据覆盖率。
在面对多线程程序进行测试时,我利用了subprocess库,同时在本单元之后我开始注意cpu运行时间的测试,并且能够在测试结束后,通过对错误行的整理分类得到错误来源,也方便了我针对性debug,而不是面对着成百上千行的数据进行人肉搜索。
在第三单元,我也初步接触了JUnit测试,但是由于过于细节化,并且我们的代码规模还没有足够大,所以利用JUnit测试的必要性不是那么强。
课程收获
通过自己亲身的磨练,我发现OO课程并没有开课前所看到的知乎回答一般血腥惨烈,反而我在学习过程中,从平台、同学以及助教的身上感受到许多的温暖。
通过一学期的学习,从刚开始接触面向对象编程的小白,逐渐也对这门思想有了些许的思考和了解,并且对我的编程能力也是有很大的提高。虽然很辛苦,每次的作业都要占据我这一周大部分的时间,先通过弱测,再不断优化性能,并且期间还要不断的debug和生成测试样例,过程很辛苦,在前几周也熬了很多夜,并且有时候结果并不尽如人意,但是现在看来,每一次的辛苦抱怨,都取得了长足的收获。同时,完成过程过程中,我也逐渐感受到了面向对象编程和性能优化的乐趣,不得不说,面向对象编程的乐趣真是要远远大于原先的面向过程编程。一次次梳理代码的结构,降低冗余度,面对自己的代码就像面对自己的孩子一样细心爱护,生怕有一点毛糙,我想这就是这门学科的有趣之处吧。
同时,也要感谢课程组的老师和助教以及一起交流讨论的同学们。相对于这学期的操作系统实验课,oo课程的体验度高下立判。界面简洁的平台,流畅的测试体验都为我们这学期的课程打好了充足的地基。同时,助教们的辛苦工作以及耐心解答,都让我们在这门课程学习中感受到了自己并不是一个人在战斗。不能忘记的是那些和我一起讨论研究的同学们,有一些同学通过自己发布的帖子,很大程度上帮助到了我,也有一些同学和我在课下积极交流,分享经验以及共享评测数据等等,没有他们的帮助,我可能每次的作业都会变得惨不忍睹。
在这里,由衷感谢所有人对这门课程的贡献,我想我在今后一定不会忘记我在这门课程上收获的点点滴滴。
一些建议
-
我认为第一单元第一次作业的难度太高了。很多同学都是和我一样,在开始前对java以及面向对象的了解和掌握程度不是那么高,一上来就面对这么困难的任务,我身边的很多同学都十分吃力,因此我认为可以适当的降低第一次作业的难度(当然,我认为本学期第一单元的作业也确实为我今后打下了良好的基础)。
-
第四单元的指导书还有待完善,表述过程中出现了很多的死角,导致在理解题面的过程中十分困难。
-
理论课程和实验课程的关联度不高,同时理论课程存在的必要性没有那么强,很多的同学对理论都不够重视的原因就是在于,理论课不听也不会影响课下任务的完成,同时在完成课下任务的过程之中,理论课的内容也就自然而然的学到了。这只是我个人的愚见。
本学期oo课程毕,诚挚感谢以下同学(不全,若有遗忘请见谅)对我的帮助:
白瑞 刘俊辰 邓皓元 何天然 申浩佳 周振源 陈一文