BUAA-2022-OO-第四单元总结
BUAA-OO-第四单元总结
写在前面
OO课程算是告一段落了,尽管我写这篇博客的时候第四单元的强测还未进行,但第四单元的架构在第二次作业时也已经铸成了。在这四个单元中,课程组尽可能地训练了我们的Java代码能力和面向对象设计思维,同时也旁敲侧击地训练了我们的一些其他技术栈,如测试能力、有效信息查询能力、博客能力、沟通与交流能力、时间管理能力等,每一周的编程作业都能确保你的生活"快乐而充实"。调侃归调侃,一个学期的训练下来,相信我们每位努力完成作业的同学都能收获满满。
一、第四单元架构设计
1.初步认识
第四单元的主要任务是在一个已经搭建好基本交互框架和类结构的基础代码上,完成一个简单的UML类图解析器。UML是一种建模语言,其语法不需要我做太多的介绍。我认为,每一位同学在开始本单元的作业之前都需要了解一下几点:
- 我们一般使用StarUML来绘制UML类图,绘制出的UML类图是可视化的,并且保存为一个
.mdj文件 - 我们可以使用记事本等文本编辑工具打开
.mdj文件,可以查看其编码 - 我们可以使用官方提供的
.jar文件实现官方数据的导出(因为我们作业的解析器不能直接识别.mdj文本,需要转化成.json文本),这有便于我们观察、测试 - 不要把第三单元的惯性思维带到本单元,例如你要注意区分
UmlElement的name字段和id字段的区别 - 我们需要完成的任务是编写一个类,实现官方给出的接口
我认为在初步接触时,了解上面的内容有助于我们快速上手。
2.设计建议
这里将给出一些设计上的建议,有的是我在开发前就构思好的,有的是写着写着发现的;有的是参考别人建议的,有的是自己想出来的。其中的某些建议或许能够帮助你多快好省地完成任务。
按层次解析UML类图元素
我们在绘制UML类图的时候,总是会先画一个类,再在类中画各种方法、属性,方法中可能还要画一些参数。一些层次比较高的UML类图元素,可能会存储一些层次比较低的UML类图元素,但是官方包封装的类中,如UmlClass、UmlStateMachine中,并没有这些存储功能。故而我们可以自己搭建MyClass、MyStateMachine类等,在里面配置数据结构存放其下层元素。
不过事实并没有这么美好,我们会发现,输入的.json文本并不保证顺序,即比如某个UmlAttribute出现前,并不保证其所属的UmlClass等高层次元素已经出现过。在这种情况下,我们是无法存储的,会遇到空指针。如何解决?我们可以按照层次高低解析UML类图元素,使用多个循环,每一层循环遍历一个层次的UML类图元素,逐个存储即可。不必担心此处的性能问题,因为测试数据的UML类图元素并不多。
解析的思路大致如上,当然,具体需要按照什么顺序来解析,我们还得自己分析UML类图的层次,避免空指针的产生。
存储UML元素的数据结构
配置好了解析方法,我们需要思考一下如何存储这些元素。在第三单元当中,我们采用HashMap存储Person等元素,构建id和元素之间的一一映射关系。本单元的元素在独有的id字段的基础上,还增加了name字段,想当我们在UML类图中看到的各种元素的名称。选择哪一个座位key更好?这需要结合我们的需求来做决定。首先,我们会发现name是一个可重复的字段,并没有严格的保证;其次,又有不少指令是通过name字段进行查询的;最后,id字段也有它独特的优势,即其是唯一的,并且几乎所有UML元素之间的关联都依靠其进行。针对这种情况,我建议采取冗余存储的方法:
- 使用
HashMap<String, HashMap<String, UmlElement>>存储一份数据,外部的String表示name字段,内部的String表示id字段,便于查询 - 使用
HashMap<String, UmlElement>存储一份数据,构建id到UmlElement的直接映射,便于UML元素之间的访问以及UML元素的遍历
另外,我们在存储的时候需要注意深浅拷贝的问题,最好只保留一份数据而存储多份引用,这也便于我们对各种元素作统一的修改。
控制代码行数:建造工具类
有的同学(比如说我),可能从一开始,就将元素的解析、存储全部放入我们需要实现的接口类中;更有甚者,将处理的方法也放在了这个类中。这一违反单一职责原则的编码方式很快就会在第二次、第三次作业中遭到惩罚。故而我建议从一开始就建立工具类,单独实现解析、检查等功能。
在我的接口实现类MyImplementation中,占用代码行数的两大部分,一是需要实现的接口函数,一是数据存储的代码及其用于访问的get()方法。对于MyImplemetation所需要完成的解析工作,我新建了一个MyAnalyzer类,配置了一系列的静态方法用于解析,这样就可以直接通过类名进行调用;同时,将MyImplemetation以this关键字赋值给MyAnalyzer的静态变量,以便MyAnalyzer对其进行访问。这样就可以讲三百多行的解析工作从实现类中剖离出来,能够有效控制代码行数;在第三次作业中,也可以建立一个MyChecker工具类,实现类似的功能。
DFS+缓存:每次作业的必备利器
几乎每次作业都有要求对图进行查询的指令。图的查询既需要有效的代码模版,又自带比较高的时间复杂度。对于本单元的作业,采用DFS+缓存的方法,能够有效解决绝大部分问题。我们可能需要DFS来遍历树甚至是有环路的图,且尽量不要自己开辟"崭新的"DFS写法,否则容易出现问题。另外,在前两次作业中,有指令可能直观上需要反复调用DFS,但由于我们输入的UML类图不具备交互性,我们可以采用缓存的手段,只分析一次图并把结果存储起来,可以有效减小程序开销(不过数据量比较小,估计也很难出现性能问题)。
仔细阅读指导书,警惕空指针
由于UML类图的元素众多且关系比较复杂,我建议在完成作业时先厘清各个UML元素之间的依赖关系,并且做出层次分析图进行记录。对于id字段的使用尤其要小心,因为要存储的元素实在太多,在有必要的地方一定要加入空指针判断。
构造数据进行测试
自动生成UML类图的数据比较困难,我主要采用手动构造的方法。我认为应当面向指令的功能进行数据构造,这些功能包括正常功能部分中的各种复杂情况以及报出异常功能的部分,虽然构造起来比较耗时,但由于UML类图的特殊复杂、联系紧密,想必搭建评测机并且构造出有效的数据也会比较困难。
3.小结
本单元的作业,总的来讲,在编码的方面来说没有那么折磨,在测试方面确实比较麻烦。想要顺利地完成本单元作业,应当在仔细多次阅读指导书的基础上和与同学交流实现方法,并且积极构造数据测试指令。
二、本学期的面向对象编程经历
本学期的前三单元,我们在层次化设计、线程安全设计和规格化设计这三个面,围绕面向对象编程进行了训练,并且在最后一个单元从模型化设计这一方面进行了学期总结性的训练。不得不说,在经历了一个学期Java面向对象编程训练后,我感到自己在以上各个方面的编程能力都有所提高,尤其是层次化设计思维的训练,让我感到受益匪浅,也让我第一次体会到了工程架构设计的重要性。层次化设计在本学期面向对象课程中是贯穿始终的,只有了解了层次化设计的核心思想,才能快速上手后面的作业。我在刚开学的时候还不理解,"为什么要把这么难的作业放在这么前面?我才写了几天的Java",但在学期结束的时候,我才明白课程组的用心。
第一单元
第一单元实现的是表达式的解析和化简,主要是训练我们向下解析的层次化设计思维。在指导书的限制下,表达式的层次化结构非常清晰,通过针对层次建立类,并管理这些类的实例是主流的解析方法,相较于线性地解析,其可拓展性和可维护性更强;以对象为目标拆化简表达式,也自下而上地将问题进行了简化。我认为自己很快就把握好了层次化的设计方法,而本单元对我来讲难点在于复杂处理的实现。譬如如期的选择、多项式展开、字符串操作、表达式解析等等,由于我不熟悉Java语言,编程基础也比较薄弱,在处理上有很多细节没有考虑到,选择的数据结构方法也很笨拙,故而遇到了很多困难。不过总体上来讲,还是大致的掌握了Java中的基本面向对象编程的特点,并且对建立层次处理问题有了一定的思考。
第二单元
第二单元通过电梯调度训练了我们的线程安全设计思维。不过我们并没有着重于锁的种类和使用,而是着重于研究线程安全的交互方法。这一点在OS课程进行到后期时,我的体会更加深刻。我的电梯调度采用了生产者-消费者模式,其对应了OS线程安全中的生产者消费者问题;除此之外,在环形电梯作业中,我还在没有意识到的情况下接触了流水线模式的线程安全设计问题。如何利用简单的锁实现对临界资源的复杂的、安全的访问?这是本单元训练的首要问题。
当然,仅仅关注线程安全设计是不能完成本单元作业的。我们还需要关注电梯系统的设计:存在横向电梯和纵向电梯,如何提高代码复用率?谁来分派请求,谁来接收请求?如何安排调度策略?我在本单元中,采用了建立继承于同一父类的横向电梯类和纵向电梯类、建立分派请求的单例类等更加凸显主动性的设计方法,这也体现了我面向对象设计思维的进一步深化。
第三单元
第三单元中,我们主要关注规格化设计,主要任务是按照JML的语言规格完成函数要求。尽管我们的任务是完成一些看起来毫不相关的函数,但在设计上我们不能仅仅完全照搬规格的写法。否则,一来规格的描述比较抽象,不适合我们直接编码;二来测试数据比较庞大,可能因性能不佳丢分。在本单元中,除了学习JML语言外,另一个重要考察的内容就是图的一些相关算法;我们需要填写的代码中并没有涉及很多层次化设计的部分。不过官方源码确实一套"非常OO"的代码,值得在空闲时间里阅读研究。
第四单元
第四单元是一个"集大成"的单元,在本单元,我们需要用OO的思维来完成一个UML类图解析器。由于UML类图元素之间的关系、层次非常清晰,因此,采用与第一单元类似的层次化设计方法是再好不过的了。如何建立类与类之间的访问、存储关系?如何建立便于查找的数据结构?如何扩展官方代码以便于完成任务?在偏向于规格的要求下尽可能地自由发挥,完善UML解析程序并通过这一过程细致地了解UML语言,是本单元的重要训练目标。不同于刚开始接触OO,此时的你已经有了对面向对象编程的模糊的概念,,也已经撰写了多篇技术博客来记录自己的OO编程经验,因此你能够更好地把握这一单元的知识和内容,为前面的训练做好总结。
总的来讲,经过一个学期的训练,我的架构设计思维逐渐由"按照功能对象建立类"转变为"观察对象的协作方式,分析架构后再建立类",最后升级到学期结束时的"建立架构和类的同时,知道自己应当遵循的面向对象编程原则以及编码时推荐采用的设计模式",我会主动去了解一些面向对象的编程原则和设计模式,将经验上升为理论,这是一个很大的飞跃。从入门摸索,到遵循经验,到最后初窥设计方法体系,我正式开始理解面向对象编程用了一个学期之久,并且还有许多东西等待我去学习。路漫漫其修远兮,吾将上下而求索。
三、工具、测试与Hack
在上学期的CO课程中,我是一个不折不扣的"伸手党",评测数据、评测机都是从讨论区"扒拉"下来的。我一方面感到内疚(当然,也不是我太懒了,是因为事情实在是太多了),一方面对分项数据与测评机的同学感到敬佩:这些数据是怎么造出来的?评测机是什么原理?如果我想写评测机,该怎么入手?很可惜,直到CO结束我也没能解决这其中任何一个问题。
到了本学期,OO和OS课程、蓝桥杯、冯如杯多管齐下,我在寒假期间不得不去学习命令行、Python以及Git等工具。不得不说,当你学习了命令行和Git之后,你的技术能力简直就是上升了一个档次,你就拥有了学习前后端、数据库等知识的基础;当你学习了Python后,你会发现他的强大超乎你的想象(尽管我认为Python编程不是很美观)。在前期,我阅读了很多博客和文章来学习这些工具知识,不过在最开始我并不是为了搭建评测机才学习这些工具的。
本学期第一单元我仍旧是手动构造数据,直到第二单元第二次作业我在强测中"失足"。想到第一单元研讨课上,有同学分享了评测机的搭建思路,我决心搭建自己的评测机。我尝试使用Python构造数据生成器和对拍装置,并且,由于有Java基础,我学习起Python来速度比较快,很快就掌握了一些灵活的处理技巧,顺利地搭好了电梯评测机。在测试自己的程序同时,我还讲评测机分享给其他的小伙伴使用。在第三单元,我也搭建了全流程的评测机,并且Hack到了他人,测试效率比阅读代码或是手动构造效率高了非常多。不得不说,其实搭建评测机并没有那么困难,而且是一件非常有成就感的事情;但它确实也是一件"水到渠成"的事。
除此之外,我也对jar包、云服务器、重定向输入等零散的知识点也有了更多的了解。大二下的课程的工具链之间的联系非常紧密,的确能够让学生全面地增长知识与技能。
四、课程收获
OO课程内的学习收获是非常丰富的,每一次作业、训练、上机都能够极大丰富我的编程基础、面向对象编程思维方法以及架构设计思维方法,给我很大的启发:从Java基本语法,到多方面的设计方法,再到面向对象编程思想的理论化总结。这些都是我从编码中学习到的。另外一大收获便是我的技术栈有了多方面的发展,这主要归功于本课程对自学的高要求,在翻阅各种各样的技术博客以及文章的同时,我也有意无意地学习到了很多东西,而且这些东西以后会经常用到(Git、命令行等)。再来,我在本课程中也提升了自己的获取信息、沟通交流的能力。OO课程的研讨课是一个很好的交流、思考平台;另外,在课下做作业时,遇到一些难以决定的地方,如何在和同学的讨论中有效输出和摘取信息,也是很重要的技能。
OO课程对于学生的训练非常全面,虽然听起来令人闻风丧胆,但不愧为一门广受好评的"魔鬼课程"。
五、改进建议
- 研讨课的小组总结分享环节,感觉很多东西大同小异,重复地讲了很多遍,可以适当压缩分享的组数,增加其他的研讨内容。并且时间实在是有点紧张,啥都没总结就要上去分享了
- 电梯单元希望能够提供官方标程的时间测试包,否则想要找出调度策略不佳的问题实在是有点困难
- 希望控制上机难度,并且设置不计入成绩的适应性上机课,第一次上机的时候甚至不知道怎么提交答案
浙公网安备 33010602011771号