OO第四单元
一、总结本单元两次作业的架构设计
第一次作业
架构
第一次作业只有类图,所以全部的UmlElement
都可以放在MyUmlInteraction
中进行存储、计算和查找。对于类图来说,可以抽象出以下的层次:
- 最底层的两个元素是类
UmlClass
和接口UmlInterface
- 在这两个元素的基础之上,可以存储它们的属性
UmlAttribute
、操作UmlOperation
、关联关系UmlAssociation
、继承关系UmlGenralization
、接口实现关系UmlInterfaceRealization
- 在操作之上存储参数
UmlParameter
,在关联关系之上存储关联对端UmlAssociationEnd
- 最底层的两个元素是类
基于此,我自定义了四个类:MyClassOrInterface
是MyClass
和MyInterface
的父类,其中存储实现一些类和接口都会有的属性或方法,如属性、操作、关联等;由于接口支持多继承而类不支持,所以在MyInterface
中实现了多继承;MyClass
中单独实现了接口实现相关的操作;MyOperation
类则主要是为了进一步层次化,为了将和操作中参数有关的计算和查询从类或接口中解耦。而在MyUmlInteraction
中,我将其简单理解为类图,实现可以通过类和接口的名字查询到对应的MyClassOrInterface
。
处理流程
由于官方包的解析会造成一定的乱序,所以最稳妥的做法是每次只处理一种UmlElement
。在将输入的全部所需信息存储到自定义的类中后,就开始针对题目中的指令做相关的计算,如寻找类和接口的父类/接口,对属性、操作、关联等进行更新。而后的每个指令的查询,就都是O(1)的了。
第二次作业
架构
在第一次作业的基础上,第二次作业添加了状态图和顺序图,这就不能简单地将MyUmlInteraction理解为类图了。所以我将三个图的逻辑解耦,分出了MyClassModel, MyStateChart, MyCollaboration三个类,在每个类中存储自己图中的底层元素并设定查询方法,如MyClassModel中查找MyClass/MyInterface,MyStateChart中查找MyStateMachine,MyCollaboration中查找MyLifeline。然后沿用上一次作业的方法,将主要的计算逻辑放在图的底层元素中,图只负责调用底层元素的方法进行计算和查找。
本次作业增加了PreCheck,主要是对类和接口进行有效性检查。由于PreCheck之前,类和接口都可能会有循环继承的情况,所以不能直接对类和接口进行递归查询更新。R001是简单的对类的关联进行检查;R002是对循环继承的情况进行检查,我采用了dfs的方法进行检查;R002是对重复继承的情况进行检查,由于已经排除了循环继承的可能性,所以在这部分就可以运用上一次作业中递归查询计算及更新的方法进行检查,再顺带进行类图的根据指令进行更新。
处理流程
在MyUmlGenralInteraction的构造函数中,调用了MyClassModel, MyStateChart, MyCollaboration三者的构造函数,对UmlElement进行读入。在后两者的构造函数的最后,对图中元素根据查询指令进行计算和更新,而对类图来说,计算更新的步骤放到了PreCheck之中——
本次作业增加了PreCheck,主要是对类和接口进行有效性检查。由于PreCheck之前,类和接口都可能会有循环继承的情况,所以不能直接对类和接口进行递归查询更新。R001是简单的对类的关联进行检查;R002是对循环继承的情况进行检查,我采用了dfs的方法进行检查;R002是对重复继承的情况进行检查,由于已经排除了循环继承的可能性,所以在这部分就可以运用上一次作业中递归查询计算及更新的方法进行检查,再顺带进行类图的根据指令进行更新。
二、总结自己在四个单元中架构设计及OO方法理解的演进
四个单元的训练,让我对架构设计和面向对象的思想有了一定的认识与了解。
第一单元:多项式求导
前两次作业的难度并不大,用比较面向过程的方法去写也没遇到什么困难,但是第三次作业难度陡增,使得我们不得不考虑将表达式抽象为树形结构,层次化地管理每一个因子。第三次作业写得压力很大,JAVA是全新的语法,面向对象是全新的思想,不过在写完之后确实对OO方法有了一定的了解,“封装、继承、多态”三者都在作业中有所体现。
第二单元:多线程电梯
电梯是一个生产者-消费者模型,模型相同,大家依然会写出不同的架构。第一次作业是单部单线程,比较简单;第二次增加了ALS调度策略,稍微复杂一点点;第三次变成了多部电梯,本以为就是多实例化几个对象的事情,结果发现事情并不简单...第三次作业中我推翻了前两次的架构,将调度器抽象出来成为一个单独的类,用来协调共享队列和多部电梯。多线程问题比想象的要复杂很多,一开始感觉有很多设计上不太舒服的点,但是通过OO思想的帮助,让我渐渐理清楚了架构里的小细节,对OO也有了更进一步的认识。
第三单元:JML
前两次的作业比较简单,也不需要复杂的架构,在第三次作业中,一个合理的架构对于变成来说事半功倍,比如将具体算法进行抽象,比如将不同的图进行归纳总结抽象出父类等等。此外,这一单元中,课程组为我们提供了拥有高质量层次化结构的官方包,处理好了输入输出,我们只需要完成剩下的简单计算逻辑即可。这次课程组为我们提供了现成的接口设计,我们只需实现即可。这既让我们有优秀的范本可以学习,也让我们对契约式编程有了比较深入的认识。
第四单元:UML
在我们对于面向对象的思想有了逐渐深入的了解之后,我们有了足够的沉淀来理解UML图,也有了一定的能力来用优秀的架构对UML图进行处理。在第14次作业中,我自认为写出了一个比较漂亮的架构。先是将类图、顺序图、状态图三者解耦,再在各自的图内按照元素的层次结构进一步做抽象。我猜如果有第三次作业,我应该是不用重构的。
三、总结自己在四个单元中测试理解与实践的演进
在OO的作业中,我们一共用到了以下几种测试的方法——
对拍
我认为在小规模的工程,特别是我们的OO作业当中,对拍还是最高效的测试方法。对拍的三步是:构造样例、输入样例、评测输出。其中除了输入样例这一步比较固定,在不同的问题中,其余两者的难度是不一样的。在第一单元的作业中,输入是多项式,可以借助java的包利用正则生成符合正则的表达式;输出也是多项式,但按照指导书方法需要代值计算,或者借助python包这一工具进行暴力的判断。第二单元的作业中,输入是用户的请求,可以随机构造,但是输出的正确性判定还是比较复杂的,感觉只是输出的正确性判定都值得单独一次作业。前两单元的作业由于第一单元涉及到化简,第二单元涉及到调度策略,都无法通过简单的比对两个人的输出进行判定,但从第三单元起,正确的输出就可以直接比较文件差异了。第三单元的输入是图,自动化构造还比较简单。第四单元的输入json...这个自动化构造还是有一些复杂的,基本就没有对拍的可能性了。
对于输入样例的构建,我认为要着重强调以下两个方面:一是对于边界情况的覆盖性,即边界情况下的正确性;二是在极端情况下程序的运行时间,即程序的性能。正确性是重中之重,是程序的根本要保障的东西,容易出错的就是各种各样的边界情况。没有性能问题的时候就不考虑性能问题,这句话是很有道理的,但是一旦需要考虑性能问题,也必须要保证可以进行压力测试。
JUnit
JUnit这个工具是在指导书中强调、老师课上强调、学长在群里强调、课上还考到的强烈安利下,进入到大家视野的一个debug工具。和对拍不同,它不是一个黑盒测试,而是针对某个方法所进行的白盒测试。JUnit非常适合对那些逻辑复杂、分支众多的方法进行测试,检查覆盖率是否到达100%,可以让我们的测试更加高效,不会像对拍一样可能一百组数据都是在对同一个逻辑进行测试,这样测试完之后对于某个方法的正确性,心里也是比较有底的。
JML工具链
这个是在JML单元的作业中用到的一种测试方法,其中包括OpenJML, JMLUnitNG等工具。其实我个人了解不是很深入,只是做了一些简单的探索、了解其基本原理而已。在对正确性要求比较高的工程中,可能这种测试是必须的,但是在我们的作业当中,考虑到效率和难易程度等问题,这种测试方式其实是不太实用的。
测试方法的演进一方面是对测试方法的数量和内容的理解。比如一开始连对拍都不会,逐渐在一次次作业中熟悉了对拍;然后在课程组的介绍下了解了JUnit,发现白盒测试在一些情况下确实比黑盒测试要好用许多;再之后第三单元中了解了JML工具链,对于诸如航空航天工程领域的测试模式有了初步的了解。
然而OO作业所交给我们的,并不仅仅只是不同的测试方法,更向我们强调了在编码完成之后进行大量测试的必要性。在这几个单元的作业中,往往会呈现出一个特点,就是编程的时间往往小于等于测试的时间:周五放出作业,一般周末花个一天就可以完成代码的编写(部分单元的魔鬼第三次作业除外),但直到周二晚上,都一直在做测试的工作。debug能力的重要性,在OO作业中被体现得淋漓尽致,也让我们通过一次次作业逐步加强了这一能力。
四、总结自己的课程收获
一学期OO,收获颇丰。
第一,学了了一定的Java语法,编程能力大有提升。Java是一门跨平台的面向对象编程语言,应用非常广泛,在OO课程中学习的JAVA,让我在今后有能力读懂代码、编写代码。特别是OO课程并不在课堂上讲语法,学习JAVA更多的靠的是有需求了就去网上找博客学习,这锻炼了我的自学能力,以后遇到看不懂的语法也不会太发憷。之前听老师说一周要编写1000行的高质量代码,仔细想了想,我好像没有一周写过1000行,但是两天写出高质量的500行应该是有的,这在半年前的我看来是不可思议的,一个学期的OO确实大幅度提高了我的编程能力,这是在C程序设计和数据结构中都没有的高强度编码训练。
第二,对于程序架构有了更深的认识。对于架构的认识和对于面向对象思想的认识,这两者其实是紧密相关的。在面向对象的程序设计中,只有用OO的方法去思考问题,才可能设计出一个高质量的架构。在OO作业中,每个单元的三次作业(第四单元为两次)就充分让我们体会到了架构的重要性。如果架构优秀,可能后续的作业就只是在之前的基础上添砖加瓦;如果架构不好,那么很有可能就要面临重构,或者至少是大幅度的改容换面。曾经还说“次次重构次次爽”,如今看来真的是“一次重构火葬场”。重构与否可能取决于对于下一次作业需求的预判,但更重要的其实还是有没有做好层次抽象,不同层次之间是否做到了高内聚低耦合,SOLID原则是否都能遵循特别是其中的单一责任原则和依赖倒置原则。唯有做好层次架构设计,程序才能是可扩展的、易维护的,才可能避免在一次次迭代中成为“*山”。
第三,了解了多线程。多线程是非常实用的一种技术,在很多场景下都有着非常多的应用。OO第二单元的作业是我第一次接触多线程,开始会觉得有一点不好理解,代码编写起来有一点复杂。但在三次作业的训练下,让我渐渐找到了多个线程共同协作的感觉,对于多线程的设计模式、不同线程的同步控制有了一定的体悟。
第四,了解了JML(Java Modeling Language)。JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言,它体现了“契约式”的思想,并让同学们根据JML规格亲手实践了契约式编程。和指导书中自然语言给出的需求相比,JML规格更加全面准确地规定了程序员必须要实现严格满足功能的方法,严格划分了“甲方乙方”的职责。虽然由于JML语言的特性,在一定程度上增加了双方的工作量,但这是一种非常规范且可靠性极高的工程化方法,其思想非常值得我们进行学习。
第五,了解了UML(Unified Modeling Language)。UML在形式上是可视化的图,但其实UML不仅仅是一个画图工具,更是一个设计工具。在软件工程中,对于一个复杂的问题,UML可以将其抽象成面向对象的解决方案,通过用例图、类图、对象图、状态图、顺序图等等不同的图来设计。它一方面让设计人员可以分解问题、理解问题、设计架构,另一方面也可以让非专业人员理解程序的结构和功能。我们的作业只涉及了类图、状态图、顺序图中的一些元素,还远不是UML的全部,但是这些精华的内容已经让我们体会到了UML图的本质,它图片的表征下其实也是高度层次化抽象化的结构,也确实唯有这种层次化才能管理如此复杂的UML图。
五、立足于自己的体会给课程提三个具体改进建议
- 发布指导书前尽量确保没有歧义。不明确需求不敢下笔只能在讨论区摸鱼挺痛苦的...写了一半被改需求更痛苦...不太清楚是不是每一个单元的作业只有部分助教负责,希望指导书在上线之前可以请没有参与这个单元作业的老师和助教审核一下,看看他们会不会有歧义或是觉得数据没有限制清楚的情况。
- 大佬优秀代码及时发布并配有一些说明。一是要及时发布,最好在博客作业截止前就能发布;二是要配有一些README说明,大佬的代码太过高级,看着看着就看不懂了。
- 可以增加对于JAVA语法的讲授或者学习引导。比如直接看三四单元的官方包,我会有很多看不懂的地方,一点一点去网上搜的话有点浪费时间,毕竟这一周的主要任务还是要完成作业,而非搞懂官方包,但官方包的确是非常优秀的学习架构、代码风格的范本,所以我觉得课程组可以依托官方包的代码做一些JAVA语法的补充,比如推荐一些博客。
一学期的OO到这里就真的结束啦,回望OO之旅,有过很多次“这作业真的是我能写出来的嘛?”的绝望,也有过很多次“这作业原来我真的能写出来!”的自豪;有过很多次深夜debug,也有过很多次和同学深入探讨;有过一个疏忽导致强测爆炸非常自闭,但也在期末总结时得到了“狼人奖”的鼓励(喵喵喵??)。OO这门课,应该是上大学以来让我收获最多、感觉最有意义的课程了,我想这和课程组老师助教们的辛勤付出是分不开的,和身边大佬们的帮助是分不开的,当然也和我自己的努力与投入是分不开的。OO之旅,体验良好,完结撒花~