OO第四单元总结
第四单元架构设计
本单元是关于UML图指令查询的实现和设计,由于UML图本身层次清晰,因此架构设计也相对较为容易。
本单元共三次作业,第一次作业仅考虑类图和针对类图的查询,第二次作业加入了顺序图和状态图的查询,第三次作业则新增对UML图一致性的测试。需要注意到的是,三次作业的指令查询均建立在静态模型之上,因此不必过多考虑第三单元常出现的容器一致性的问题,使得架构设计更加简单。
还需要注意的一个问题是性能问题,本单元的运行时间较为宽松,但并不是毫无限制。具体点说,就是本单元中的查询指令并不像JML单元中不能出现O(n2)的复杂度,但是依然需要对复杂度进行控制,尤其是有关于接口的查询,由于接口的多继承性质,如果不进行必要的剪枝,在某些极端的情况下很有可能达到指数级别的复杂度,这是绝对不能接受的。
综合以上三点,三次作业的架构设计如下
第13次作业
本次作业共7个类,除了一个主类MainClass
以及一个要求实现类MyUmlInteraction
外,其余的类均属于在官方包的类的基础上进行修改的类,层次结构如下
层次 | |
---|---|
1 |
UmlClass 、UmlInterface
|
2 |
UmlAttribute 、UmlOperation
|
3 | UmlParameter |
在具体实现上,由于需要同时满足对于在构建时按id查找的需要和查询时按name查找的需要,我基本上对于每一种类都设计了两个HashMap
。如对于UmlClass
,我设计了HashMap<String, MyUmlClass> classes
和HashMap<String, ArrayList<MyUmlClass>> name2classes
来满足不同情景下的查询需要。
具体的UML图如下
第14次作业
本次作业共14个类,层次结构如下
类图层次 | 顺序图层次 | 状态图层次 | |
---|---|---|---|
1 |
UmlClass 、UmlInterface
|
UmlInteraction |
UmlStateMachine |
2 |
UmlAttribute 、UmlOperation
|
UmlLifeline |
UmlState |
3 | UmlParameter |
UmlTransition |
|
4 | UmlEvent |
由于在本次作业中UmlStateMachine
和UmlRegion
是一一对应的关系,因此取消了UmlRegion
以减少层数;另外,我将预处理的过程抽取出来形成一个新的类MyPreTreatment
,在查询指令的类中只留下必要的容器,使结构更加合理。
具体的UML图如下
第15次作业
本次作业共16个类,层次结构如下
类图层次 | 顺序图层次 | 状态图层次 | |
---|---|---|---|
1 |
UmlClass 、UmlInterface
|
UmlCollaboration |
UmlStateMachine |
2 |
UmlAttribute 、UmlOperation
|
UmlInteraction |
UmlState |
3 | UmlParameter |
UmlLifeline |
UmlTransition |
4 | UmlEvent |
本次作业中对于新增检查需求新增MyCheckRule
类,专门对模型进行检查,降低耦合度。
具体的UML图如下
四个单元中架构设计及OO方法理解的演进
第一单元
第一单元的任务是表达式求导,通过指导书可以比较容易抽象出表达式、项、因子、指数等等不同的结构层次,如此多的层次相互作用,循环作用,如果使用面向过程的思想是很难解决的。
第一单元的作业本质上是对于面向对象思想的初探,三次迭代作业不仅要考虑层次结构是否合理,还要考虑到层次与层次之间是否足够解耦,也就是说是否能够轻松拆装,从而实现迭代性的要求。
在第1、2次作业中我均使用正则表达式进行层次的拆分,这是可行的,但是在第3次作业中出现格式检查,这使正则表达式的使用出现很大的隐患,因此在第3次作业中我吸收递归下降的思想,对我的代码进行大幅度的重构,可惜没有再次迭代的机会了。
第二单元
第二单元的任务是多线程电梯。多线程的实现可以采用JAVA自带的synchronize关键字来进行临界区的设置,从而完成线程同步互斥。在电梯调度算法方面,我采用了多级调度的方式进行,在3次作业的迭代方面都没有太多改动,迭代性较为良好。
第二单元实际上是四个单元中让我感到最为困难的一单元,这是由线程调度的不确定性导致的测试、调试的困难性,以及程序演进的动态性,从而导致思考的抽象性大大增加。多线程是面向对象这门课最重要的知识点之一,同时也是工业界的大难题。我认为,如何实现线程之间动态地、实时地交互,这是面向对象最为优越于面向过程的方面。
在本单元的学习中,我逐步认识到了面向对象不是仅仅封装、继承、多态三个词就能概括的,多线程的学习使我清楚地看到了面向对象的价值。
第三单元
第三单元的任务是JML,这一单元由于仅需要实现JML规格规定的方法,实际上并没有对架构的要求,只要对规格描述进行一些优化就可以完成任务。
第三单元主要是体会规范化代码的编写,同时考察性能优化算法的使用,如第一次作业中使用的并查集,第二次作业中使用的缓存优化思想,以及第三次作业中使用的堆优化dijkstra。
第四单元
第四单元的任务是UML图解析,本单元的架构设计在上文均已叙述。
第四单元与第一单元比较相似,层次结构比较清晰,但是可以明显感受到第四单元任务的编写相比于第一单元轻松不少,这一方面是因为第四单元没有性能分,另一方面也是因为自身面向对象设计思想的提升。
四个单元中测试理解与实践的演进
第一单元主要采用数据生成和sympy库对拍的数据测试方法,利用暴力生成测试点的方式进行自动对拍,在手动构造测试样例方面有所欠缺。
第二单元主要延续了第一单元的数据生成思想,但是由于多线程程序难以调试和复现,自动测试的测试点一般较弱,所以在自动测试的同时采取手动测试,利用命令行输入测试点,再利用对拍机进行调试。
第三单元由于不便于自动进行答案的检查,于是选择和同学们一起对拍,通过阅读往年博客找到有可能出现问题的地方进行有针对性的构造测试样例,同时针对每一条指令进行覆盖性测试,保证正确性测试。另外,我对于JUNIT也有所尝试,但是由于工具链仍不成熟、单元测试缺乏对整体性的考虑等特点,最终放弃了。
第四单元的测试数据仍然采取自动构造和手动构造相结合的方式,利用小组对拍进行错误测查找bug。另外,我还尝试通过对StarUML安装插件来支持Java程序的反编译,用Java编写类图的方式来构造测试样例。
课程收获
-
对于面向对象思想的深入理解
通过一个学期四个单元12个周期的反复翻滚,我对于面向对象的思想算是有一个整体的把握了。现在再看第一单元的指导书,已经没有当时两眼一黑的感觉了。更进一步地,由面向对象思想展开而产生的各种设计模式也有所了解(最起码有所耳闻)。同时,由于面向对象的思想更加贴近工业界的生产模式,借助着研讨课的dl们的分享,我也对实际生产有了一些了解。
现在再面对一个复杂问题,在面向对象的思想的指导下,我有把握对其进行拆分、解耦、内聚、组合,进而抽象出层次清晰、可维护、可迭代的架构,然后再进行有良好代码风格的代码的编写。
-
对于各种工具的使用和相关知识
最重要的就是JAVA编程语言的使用,虽然这门语言是我自己在假期自学的,但是在OO极大压力的推动下,我现在已经基本掌握了JAVA语言的编程方法,对于用过的容器能够熟练运用,对于没用过的容器也能够通过官方文档快速熟悉。
其次是评测机的编写,在上学期计组的讨论区中,已经有dl分享了评测机和许多测试点,也是多亏这些测试点我才能幸运地闯过计组的难关。在本学期OO课程组的推荐和引导下,我能够完成比较简单的评测机的编写,这在上学期看来是难以想象的。
还有就是一些零碎的小工具,比如StarUML、JUNIT等等。
-
心态上的变化
OO的每一次指导书的发布都是一次折磨的开始。平心而论,规模相当的JAVA程序的编写和调试都要比上学期Verilog程序的编写和调试容易许多,但是由于需求上理解的客观困难和时间上的紧迫性,我在这学期实际上感受到了比上学期更大的压力。但好在一切都过去了,现在的我已经不是当年的我了,我的心理也已经足够强大了。
具体改进建议
-
面向对象的味可以再浓一些,可以更加贴近实际生产环境
臃肿不是面向对象,面向对象要消灭臃肿,取而代之的是清晰的层次结构以及良好可迭代的架构设计。
就各种设计模式来说,我相信它们的实际使用场景一定是在生产环境下,而不是在学校里个人完成所有代码编写的环境下。例如工厂模式,其本意是对创建对象的方法进行封装,用户想要创建一个对象,只要调用接口中的方法就可以了,而不必理解内部方法的实现。但是在个人完成所有代码编写的场景下,我本身就是已经了解所有方法的实现的,当方法的编写者和调用者是同一个人时,使用工厂模式只会让我感到臃肿,这不是面向对象优越性的体现。
因此我认为,要想让同学们在这一方面真正体会到面向对象的优越性,可能可以有两条途径。其一是增加代码的规模,同时适当地延长生产周期(充满其它学科ddl的4天还是太少了),这样可以更加充分地思考代码的架构,而不是疲于完成作业。其二,则是更加激进和不成熟一点的,是否有可能真正让同学们使用别人的代码来迭代,也就是说,调用别人的代码中的接口,在别人上一次作业的基础上进行迭代开发。这样,一个同学通过强测的代码可以由多人来使用进行迭代开发,一个人在进行迭代开发时需要对多人的接口进行调用。或许不必设置互测环节,而是同学们在相互迭代开发的过程中相互寻找bug,在找到bug后利用一个测试点进行hack,并将测试点和bug返回到原先编写接口的同学那里进行bug修复。而一个人面对多个程序接口,只要能通过一个接口将程序完成就可以当做作业通过,由于如果面向对象编程,换一套接口就像喝水一样简单。在代码编写结束之后,每个同学都可以对他使用过的多个接口进行打分,这样就迫使调用者和被调用者都必须按照生产环境中的调用者和被调用者分离的环境下来进行面向对象的编程。当然这一想法还太不成熟,还需要不断地进行完善和修改。
-
JML与UML相结合
这一点主要是因为第三单元和第四单元的作业中关于JML和UML的知识太少。第三单元的作业,只要学过离散数学、会几个英文单词就可以完成作业的编写,JML会写了吗?不会。第四单元的作业,简单知道图的层次,会读mdj就能完成作业的编写,UML图会画吗?知道什么时候该用什么箭头吗?不知道。
因此,问题就是,作业对于理解JML和UML的导向作用不够突出,仅仅完成作业,可能只是对JML和UML有了最最初步的了解,而距离掌握JML和UML还差得远。换句话说,在繁重的课业压力下,当同学们对JML和UML了解到能够做作业的程度之后,便不一定会继续深究(dl除外),可能有一部分人在期末结束、压力不大的时候再补上这一块的知识,但是一定会有相当一部分人不会再看JML和UML,从而导致掌握情况不够好。
针对这个问题,我目前的初步想法是结合JML和UML,利用JML严谨的叙述来辅助理解UML(当然可能很不成熟)。这样做的好处有几个。第一,解决JML单元和UML单元反差过大,JML单元时,身边总有同学说JML规格“不说人话”,明明用自然语言描述的需求非要写出一大堆规格,而当我们适应读规格之后,突然进入UML单元,由于UML单元的需求过于模糊,与JML的严谨性严重不符,因此在第13次作业的讨论区有许多同学在问异常抛出顺序这样一般人看来可能是显然的事情。第二,提高对于UML图的理解,当然使用JML可以对UML的方法进行精确描述,这样可以消除大部分讨论帖的动机,这样做还可以对UML图的层次有一个更加明确的梳理,不至于到了提交时间的前一刻还觉得自己的理解不够到位。第三,如果将JML与UML进行合并,将会空出一个月的时间,可以就此延长作业的迭代周期,使同学们有更充裕的时间来思考和编写代码。
-
许多同学包括我都非常关切的其他问题:实验课课后给答案,不给答案的练习就是耍流氓;每单元的第一次作业之前最好能预备一个预习课(或是将训练弄得更加贴近实际作业需求,就几道填空选择是远远不够的);与OS等其他专业课(主要是OS)之间的协调,能够给同学们更加充裕的时间来同时进行OO和OS的学习,不然OS考试时间完美与OO的互测时间相冲,那大家普遍互测不积极也是情有可原的吧[吃瓜]