面向对象第四单元总结
第四单元总结
本单元概述
本次作业考察UML相关的内容,目标学会熟练使用UML进行软件设计并使用模型化思维对数据进行建模和管理,并能深刻体会模型化设计の奥义。
- 第一次:UML入门级的理解、UML类图的构成要素,编写解析方法(支持对类图的解析)
- 第二次:扩展解析器,使得能够支持对UML顺序图和UML状态图的解析
- 第三次:支持对模型进行有效性检查
作业架构设计
public class UmlTreeNode {
private UmlElement elm;
private ElementType type;
private String id;
private HashSet<UmlTreeNode> sons = new HashSet<>();
private UmlTreeNode parent;
private String name;
/* ... */
}
采用装饰者模式,其允许向一个现有的对象添加新的功能,同时又不改变其结构,而仅作为现有的类的一个包装。就增加功能来说,装饰器模式相比生成子类更为灵活。此次将原来的UmlElement类型包装起来进行拓展为UmlTreeNode,并将其实现类继承此装饰类,使这种策略,可以把原有的稀少的,功能不够全面的结构,用新的,具有强扩展能力的类包装起来(本文参考)。例如,可以增加设置、获取其具有parentID作为id的结点元素(父节点)的功能,同时存储以此结点元素的ID为parentID的结点元素(子节点)。
对于第一次作业,在UmlTreeNode下有UmlClassLevelNode、UmlLizeNode、UmlStaticNode、UmlOptNode子类,而UmlClassLevelNode下又有UmlClassNode、UmlInterfaceNode两类。
除此之外,我还建立了NodeFactory类用于对不同类型的UmlElement进行分类实例化我的装饰者类并组织存储,同时再循环访问所有UmlClassNode、UmlOptNode和UmlInterfaceNode类型的元素,以向其添加静态元素属性(UML_PARAMETER和UML_ATTRIBUTE的装饰者类都是UmlStaticNode)。
其中,我对于结点对象的存储方式比较丰富,便于各种查询添加,例如:
private final HashMap<String, ArrayList<UmlStaticNode>> umlStaticHM = new HashMap<>();
//以其parentID作为key的静止结点,可以记录每个同父节点的子节点。
private final HashMap<String, UmlClassLevelNode> umlClassLevelIdHM = new HashMap<>();
//以自身id为key的class或interface结点,用于遍历查询。
private final HashMap<String, ArrayList<UmlClassNode>> umlClassNodeNameHM;
//以自身name为key的class结点,用于判断重复异常,同时此图会被传出到MyUmlInteraction。
private final HashMap<String, UmlClassNode> umlClassNodeIdHM;
//以自身id为key的class结点,传到MyUmlInteraction后用于查询。
对于第二次作业我仅添加了UmlInteractionNode、UmlLifelineNode、UmlMessageNode、UmlRegionNode、UmlStateMachineNode、UmlStateNode、UmlTransitionNode类以达到查询需求(实际上还有一些冗余,例如UmlEvent并没有方法涉及,但我设计了接口)。
对于一些需要遍历搜索的方法,例如getImplementInterfaceList,在第一作业中我使用BFS查询元素,并将方法放于MyUmlInteraction中,这使得其十分冗长,于是在第二次作业中,对于需要BFS查询后继状态的方法,我将其放在了新添加的类中作为结点的方法,同时得到的结果作为结点的属性,这样可以避免重复查询。同时我添加了ExceptionJudgement类专门作为抛出异常的实现类,还有UmlMethods类用于存储MyUmlInteraction中的实现过程方法。
对于第三次作业,我在ExceptionJudgement类中添加了抛出异常类型,其中频繁使用了之前存储的各种HashMap以及BFS来遍历搜索。例如对于R002,我分别遍历了umlClassNodeIdHM和umlInterfaceIdHM,对于每一个结点都使用BFS判断其是否可以找到一个环回到自己从而说明其是否重复继承。对于其他几种规则都是如此,总的来说都是利用本身装饰者类的功能和属性来查询判断,如果有更多需求,添加即可。
架构设计及OO方法理解的演进
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。
面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。 ——Wikipedia
四个单元的内容是由层次化设计 ➡️ 线程安全设计 ➡️ 规格化设计 ➡️ 模型化设计的逻辑进行的,而其内在又蕴含了OOA(面向对象系统分析)和OOD(面向对象系统设计)两方面的内容。
- 面向对象的分析方法是利用面向对象的信息建模概念,如实体、关系、属性等,同时运用封装、继承、多态等机制来构造模拟现实系统的方法。
- 面向对象设计方法是面向对象程序设计方法中一个环节。其主要作用是对分析模型进行整理,生成设计模型提供给OOP作为开发依据。OOD包括:架构设计、用例设计、子系统设计、类设计等。
四个单元都贯穿了这些理念,只是各个侧重点有所不同。
多项式求导单元,我抽象化出了Polynomial多项式因子、Monomial单项式因子、SinFactor以及他们的共同继承的父类Factor等等模块,其间我运用了封装、继承、多态、接口等机制,这是OOA方法的体现,同时对模型的模块化抽象体现出面向对象的层次化设计,这与表达式树中类与接口的构造关系都是OOD思路的一环。多线程电梯单元,我们在多线程场景下学习面向对象思维、解决线程安全问题、理解消息传递机制,这些在现代计算机系统中是不可或缺的组成。在JML单元,规格化设计将运行细节抽象为用户所需求的行为,体现了面向对象的关注点。UML单元中,图形化设计明确、清晰、唯一,更便于交流、维护,有效性检查确保了交流有效性。
每个单元三次作业的递进也可以解释这些设计哲学。
Walking on water and developing software from a specification are easy if both are frozen.
走在结冰的河边不会湿鞋,开发需求不变的项目畅通无阻。 -Edward V. Berard
OO作业的三次作业是难度的提升,是需求的改变,现实中这种变化是普遍发生的,做到
- 面向对象
- 复用
- 能以最小的代价满足变化
- 不用改变现有代码满足扩展
这几点,说明正在落实OOD,而满足这些原则能使你的代码在面对需求改变时十分灵活。当然,更抽象而具体地讲(ಡωಡ),SOLID五原则是一个十分完整的归纳。
在我的四个单元中,前三个单元都有一定程度上甚至大规模(第二单元)的重构,这带来了巨大的时间成本,但在我完成第四单元作业时,首先从后续可能的改变入手,建立了一个更完整,有冗余的架构,这样在我完成后续的作业时,我只需要向里面填充方法即可,这既降低了难度,又降低了犯错的可能,完成之后还能感叹:可能这就是面向对象吧,i了i了。
测试理解与实践的演进
对于测试,我从↘️

到↘️

再到↘️

只用了一次作业(没测试自己的程序而被强测和互测锤爆,没写测试程序而没法爆锤别人)
测试的真的很重要!(但是有一个前提是要先按时完成了作业...
测试主要分为构造测试数据,运行测试数据和对比结果三个方面。因此对于黑盒测试,主要用到的工具无外乎就是构造数据的python代码,运行程序的脚本代码和比对结果的python/脚本代码。而对于单元测试,那就是JUnit了,编写其代码的方法网上比较全。下面我大致说明一下历次作业的测试思路:
第一单元作业在强测结束后做了马后炮,照着别人的思路写了一个。使用了python中的Xeger函数,根据正则表达式随机生成相应的字符串。然后利用管道编译java文件对其输入输出,最后利用sympy中的求导函数diff对正则表达式生成的数据求导,最后按照评测机的测评方法,比对python求导后的值与程序跑出的结果,验证程序的正确性。不过测试出别人bug的数据都是手动撸出来的...
第二单元作业我用python完成了一个定时输入程序,可以向打包好的jar输入完整格式的请求。并且编写了判断结果正确性的程序,主要用是否将乘客送到终点、是否无中生有、是否电梯吃人、乘客是否穿墙等条件判断。在debug时,由于调试和在IDEA里面运行都效率极低且无法复现出问题,我逐渐领悟出了在代码间插入输出,然后用自动化测试工具运行的方法,效率大大提升。
第三单元作业使用python构造数据集,因为有明确的命令条数和格式要求,所以比较方便实现,注意需要用数组存住生成的person来构造环。需要使用他人的代码进行对拍,同时使用脚本比较文件(fc命令)。
第四单元没测试,因为不好构造数据
。
总的来说,黑盒测试能在很大程度上解决问题,但是有些时候构造数据的程序针对性不强,导致效率十分低,同时需要找别人的程序对拍,这是一个缺陷。所以黑盒测试只能拿来找到编码上的、对题意理解不当的和比较明显的程序逻辑上的问题,JUnit测试同样只能解决上述bug。而对于情况未充分考虑的以及性能问题则需要形式化验证,计算和思考,这其中,需要积极与同学讨论,多查资料,关注数据结构细节。
课程收获
你以为我是面向对象编程,其实我是Java哒!
你以为我是Java编程基础,其实我是写脚本哒!
你以为我是让你编程,其实我是数据结构和算法哒!
这真的是一门内容及其丰富的学科,为了掌握这门学科,我深入理解Java,熟练编写廉价python和shell,markdown从入门到依赖,看了无数篇图论相关解题博客,甚至学了一手Html+CSS+JavaScript(好吧不是为这个学的)。
但是最重要的是,我第一次感受到了编程工程量的压力。我之前很少会做不完家庭作业,然而在OO课上,我光电梯单元就有两次没做完!!!这不禁让我反思自己的学习模式,我总是先关注实现细节,先思考个一天半,一直动不了手,改了一版又一版思路,一直推到周四周五才开始写,结果电梯单元就是第一次作业极限操作,第二次甚至没有交过,第三次痛定思痛,却还是大错特错。literally,我哭了。可能还是水平跟不上想法,想的太复杂,自己是做不出来的,人要有自知之明。与其思考千万遍,不如先动手实践,于是第三单元故意从简,而老师考察重点刚好在于性能,我...

最后一个单元还是拿了一次强测满昏,算是挽尊了。
OO课是十分现实的,你只有付出得多,用心踏踏实实写才会把成绩做得好看,不要投机取巧以为有巧妙的算法一步登天。
OO充实而有深度的内容让我深刻体会到面向对象的博大精深,听老师讲课胜似和苏格拉底探讨哲♂学。想必在今后的人生路上有一天,我会因为现实与面向对象设计哲学的偶然巧合而会心一笑吧。
改进建议
看了一些前辈们去年的博客,感觉大家的诉求也比较相似,然而在一些方面并没有太好的落实,我想这可能是因为看同学们改进建议的主要是助教,但是他们大多在第二年不再担任下一届同学的助教,这中间的意见传达可能出现断层(不过我并不知道本届将成为助教的同学会不会看,仅作推测),比如,许多同学反应的各单元第一次难度较大的问题并没有改变,而且学习难度更陡了...

所以要我提出什么改进的话,我可能首先会说多采纳同学们的建议吧。其次,我觉得对于理论课程来讲, 它和作业结合的还不是特别紧密,有点过于抽象了,很难有比较深的理解,所以我觉得应该先讲一次实践类型的课程或者先做实验、重点介绍实验内容、作业内容,做完作业之后再抽象总结出理论内容,这样回过头来能对理论知识有比较深层次的理解,如果先高屋建瓴的讲,做作业的时候会比较摸不着方向。嗯就酱。
线上学习的体会
这可真是一言难尽。其一,这门课并没有像有些以理论教学为主的课程一样变水,而且,从去年前辈们的博客可以看出,作业难度增加了,所以整体而言,OO的学习过程并不快乐இ௰இ🀁;其二,在家学习的效率降低了,这不单单体现在我在家更懒或者我更爱摸鱼了,而是本来在学校里丰富讨论渠道变得狭窄了,在学校学习时我们往往有大量线下讨论机会来解决知识难点,而在家只能自己专研或者上网讨论,这样很可能仅仅因为关注不够及时而错过一些能够解决自身问题的机会。例如,在讨论区中经常有Q&A,这些问题往往存在共性,很有可能自己一直想不通或者想不到的问题能够在里面找到解答,但是如果没及时关注讨论区就会败北,因此线上学习的不便是比较明显的,但是对于这门课程而言,其教学质量和内容的丰富程度还是非常有保障的,总体来说学习体验不错,在此感谢老师和助教团队的辛勤付出。

浙公网安备 33010602011771号