面向对象第四单元暨课程博客总结

面向对象第四单元暨课程博客总结

前言

在经历了四个单元的洗礼后,面向对象课程也落下了它的帷幕。回顾这一学期以来的丰富的课程内容,从老师教授的理论知识、精心设计的增量式作业以及与理论课程密切相关的实验课程,不得感慨收获颇丰。在第四单元里,我们接触了UML (Unified Modeling Language),从一个更加宏观的角度去了解现实之中架构的搭建与设计。这一单元的任务即是完成对于UML图的解析,难度与工作量较前几个单元而言都有所降低,大多集中在对官方开源代码的阅读与理解。

第四单元总结

第十三次作业

本次作业要求我们实现UML类图的解析。UML类图中包含了每个类各自的属性,以及类、接口之间的关联关系、继承关系等。此次官方包的几个主要用的类的属性如下图所示:

其中,id是用于区分各个Element的唯一标识,通过对每个UmlElement中由parentIdsourcetargetreferenceend1/2id的索引可以表示出UML图中原有的树形结构,该结构如框架下图所示:

因此,本次作业实质上需要完成的工作即为从该UML树型结构中,提取信息,恢复其在Java中的原貌,完成相应的查询指令。程序类图如下:

通过实现中间结构MyUmlObj,通过两次读入时的while循环,完成了由UML树型结构到Java原始结构的转换。在MyUmlObj中存储了相应类/接口的属性、方法以及继承关系,并在MyUmlInteraction中有根据id到相应MyUmlObj的映射关系。

本次作业较复杂的查询分为两类,分别为对关联关系的查询,以及对实现接口的查询。由于关联关系在计算时涉及到对整个图结构的查询,因此并没有将相应的关联关系存到对应的结构下,而是以HashMap<String, HashSet<String>>的映射来存储关联关系,用HashMap<String, Integer>存储reference在对端中出现的个数,其中的字符串对应各个类或接口的id。在统计时只需要查询自身与父类的相应数据进行加和即可。对实现接口的查询,则分别涉及到类与接口的继承,通过BFS算法可以将所有实现的接口依次找出。

第十四次作业

本次作业在上次的基础之上增加了对于状态图与顺序图的解析。考虑到对于测试数据最终增加的诸多限制,本次作业难度甚至比起上次作业都有所不如,增加的更多是涉及的广度。其中,新增的要用到的几个类即其属性如下图所示:

可以看出,与上次类似,本次仍是通过对id的索引构成的UML的树型结构,结构框架如图:

由于本次作业涉及到了三种不同的UML图,因此在原来两层架构的基础之上新增了最外一层的抽象,类图如下:

本次作业的难点,仍然集中在两个方面,一是在原则性检查时检查是否具有循环或重复继承,二是在计算状态图后继状态时对不同状态的处理(尽管后者在数据限制后难度大大降低)。状态图在不涉及初末态合并以及转移去重之后,基本变为了简单的计数,直接使用BFS即可,不过值得一提的是,因为一个状态的后继状态默认并不包含它本身,于是在实际初态计算时,应当向队列中加入待查节点的相邻节点,以免算上自己,而其余部分此处便不过多赘述。

此处重点介绍原则性检查部分。

  • R001主要检查有无重复成员变量,其中包括与其关联的对端名,需要注意的是对端名与对端的类名是两个概念,个人理解是对端名可能代表的是对端类或接口在自身之中的相应表示,因而命名需要与其他成员变量区分开来。
  • R002是检查是否存在循环继承,固然对每个节点依次进行深搜可以完成对该项规则的检查,但是这样做效率未免十分低下。开始时,笔者尝试使用拓扑排序将不在环上的结点全部剔除,但是这一方法只适用于仅存在单继承的类间继承关系,在存在多继承关系的接口之间可能无法正确判断某节点在环上或者仅仅是与环相连。由于在有向图中,强连通分量与环是等价的,因此可以使用寻找强连通分量的tarjan算法来计算图中的环。tarjan算法即是在DFS的基础之上,增加了结点栈与访问次序,以确定当前正在递归访问的结点归属于哪一个强连通分量。
  • R003检查是否有重复实现接口,此处为了省事之间对每个类/接口调用上次的实现接口查询,判断是否存在重复。

第四单元小结

第四单元战线拉的其实有点过于长了,还与烤漆交支在一起,导致最后回顾时不免觉得有些虎头蛇尾。本来这个单元还是有许多可以挖掘的地方的,比如UML作为设计层面的一种抽象,它的不同种类图表示之间有什么样的联系,是否保持了设计的一致性等等。感觉课程组本来构想了一个比较宏大的框架,最后因为时间不够不得不加上诸多限制,然后第十四次作业就变得有些索然无味。但是,就本身解析UML图的过程而言,还是有许多收获的,无论是对于UML本身的理解,还是对于从需求出发,如何与具体设计实现相对应等更高层次的抽象结构,都在本单元得到了体现。

课程总结

架构设计及OO方法理解的演进

第一单元——表达式求导

  • LV1:初识面向对象,本质面向过程的套壳式选手。

    第一次作业只是初步了解了面向对象的大体框架,熟悉了Java语言中正则表达式的使用,几乎没有什么架构与方法可言。

  • LV2:试续已有架构,但却不留后路的零扩展选手。

    第二次作业开始使用三元组对新增的三角函数项进行抽象,使其与第一次的作业架构相对应,但是小小的脑瓜中装不了大大的作业规划,导致架构在后续一次作业中只能推倒重来。此外,本次作业中极为拙劣的加入了简单的优化部分,但是并未与其余部分分离,导致架构十分丑陋。

  • LV3:无奈破而后立,初探工厂模式的贫优化选手。

    第三次作业在处理输入时借鉴了词法与语法分析的思想,将复杂多项式按照树型结构存储,以便后续递归计算。更重要的是,通过让实现一个抽象类完成了对于不同类型的项(子类)之间运算的统一,对于接口与继承逐步开始有了一些理解。在将复杂运算解耦之后,其实每一个按规则求导运算的实现都非常的简单。如果说第二次作业是对三角函数项根据特定规则向普通项映射的一种局部抽象,本次作业的抽象则将所有项进行封装,使其完全统一的整体抽象。

  • 收获装备:组合模式。将对象组合成树形结构以表示"部分-整体"的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。总而言之,是将复杂问题进行抽象,使其在表层与简单问题逻辑一致。

第二单元——多线程电梯

  • LV5:单部电梯傻升降,选手初探多线程。

    第五次作业是对于多线程的第一次探索,实现的傻瓜电梯基本没有什么难点,输入处理、调度器与电梯三个线程之间也相对较为独立,通过共享请求队列来完成任务传递。

  • LV6:开门闭门何时入,捎带电梯怎调度。

    第六次作业被自己充分的不准备坑惨,详见。由于对多线程的掌握不熟练,以及调试时间不足,出现了许多线程同步方面的问题。测试后改版的架构与改版前的主要区别,即在于捎带的判定功能位于电梯内部还是调度器内部。由于在调度器内部实现对电梯完全的远程操控几乎是不可行的,对捎带时机与结束判定都存在许多问题,因此后来改成调度器简单分发,电梯自行处理捎带的结构。具体即是在电梯内部增加一个捎带队列,并且完成对其任务顺序的维护。本次作业要求的任务其实并不复杂,反而是测后重构时判断问题的过程,让自己对线程如何同步,以及线程间低耦合的重要性拥有了更深入的认识。

  • LV7:几梯并行谁人搭,三楼换乘一枝花。

    第七次作业中,电梯变为了三部,也因而产生了任务分类与电梯换乘的需求。主体架构其实一直延用第一次作业定下的基调,三类线程两类请求队列,而换乘对整体架构的影响也并不大,通过对几个核心换乘楼层的选定即可较优的解决所有换乘问题。在任务分配方面尽量做到负载均衡,将三部电梯公共的任务集尽量向相对空闲的电梯进行分配。对于任务拆分,则在优先队列中新增了孪生任务回传机制。总体而言,本单元的架构相对固定,每一次作业都是对前一次作业功能上进一步的完善。

  • 收获装备:生产——消费者模式。该模式包含了两类线程,生产者线程用于生产数据,消费者线程则用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享对象来让生产者或消费者实现各自的生产消费行为而不必在意另一方的行为。在本单元作业中,通过wait()notify()方法来实现线程之间的消息通信,从而使生产消费者间的正常协作。

第三单元——JML实现

  • LV9:没有规格,不成方圆。

    从第九次作业开始接触到规格化的编程,从这个单元开始的作业不需要我们实现完整架构的设计,只需要实现开源包中对应的接口即可。本次作业实现了一个极为简单的容器,但对规格的实现仅仅停留在简单翻译的层次,逻辑基本相同。

  • LV10:世上本没有路,查询的多了,也便成了图。

    第十次作业是在前次作业的基础之上,实现一些最基本的与图相关的查询。在数据结构上为了提升查询效率,将前述作业中的ArrayList大多都替换为了HashMap,同时还增加了缓存机制,本质上是利用额外的空间来赚取时间上的效率。此次作业中对规格的理解也进一步的加深,在具体实现时只需要保证代码与规格间的等价性,而不必苛求其逻辑完全相同,构建基于功能需要且适应性能要求的中间数据模型和协同架构。

  • LV11:你知道吗:人在看到站台编号时,人脑的左前额叶皮层会根据该数字与5的倍数的关系,来判断该站台是否拥挤,从而让人们在更加拥挤的站台下车。

    第十一次作业继续在前述作业的基础之上,增加了新的需求,将抽象问题具体到地铁线路的查询。实际上,几个看似不同的查询需求的区别也仅仅是图上权重的不同,通过实例化不同的图统一计算即可。通过拆点将有换乘需求的查询与之前已有的架构进行统一,也同样体现了简单的组合模式的特性。这一单元每次作业的层层演进都体现了在实际设计中,产品需求迭代更新,不断丰富的特点,也帮助我们认识到了可扩展性在进行初步架构设计时的重要性。

  • 收获装备:规格化。良好的规格不论是在自行设计,还是在团队合作中,都能起到极好的规范作用,让我们写出的代码清晰明确、方便测试且易于管理。不论是由规格出发,在一个规整的框架之内去写代码,还是从代码出发,去总结提炼规格,都是对我们工程化的一种极好锻炼。

第四单元——UML解析

  • LV13:你爸爸(_parent)并不是你爸爸。

    第十三次作业开始进行对UML模型的解析,实质上需要完成的工作为从该UML树型结构中,提取信息,恢复其在Java中的原貌,完成相应的查询指令。因而在读入时,会按照相应的逻辑关系将属性、方法与关系还原到相应的中间结构下,以便后续进行查询。但是对于面向对象方法的理解,则更多的在于对UML类图本身的学习,其对象化的语义表达、对于整体架构更高层次的面向设计的抽象等,都非常值得学习。

  • LV14:“这是相当厉害的简化了。”

    第十四次作业扩展了上一次作业解析的模型范围,引入了状态图与顺序图,但是简化后的作业对于理解上来说已经没有了什么困难。给人更多启迪的是老师上课所讲述的UML建模方法,不同种类UML图之间的关系与一致性检查,功能、结构、行为与部署四种模型视图,以及模型化设计的思维方法。

  • 收获装备:UML模型。UML提供了一种面向对象式的抽象且直观的可视化描述逻辑,把系统抽象为类与类之间的协同,通过可视化模型来描述和展示系统功能、结构和行为。从需求的角度出发,完成对架构的设计与搭建。

测试理解与实践的演进

  • 第一单元:唔,你越界了。

    由于第一单元中的测试包括合法与非法输入,涉及到对于Wrong Format的检查,因此在构造课下测试样例时,不仅要构造符合要求的测试样例来判断程序能否正常工作,此处利用了python的自动化正则表达式工具,还要构造各种错误样例来检查程序是否能够接纳异常情况,如空字符,括号匹配等问题。然而,本单元强测中失掉的分数还是集中在了无法正确处理某些错误情况上,警示我在未来编写程序时,一定要先自己先全面的考虑到各种可能出现的情况。

  • 第二单元:不测就会不测。

    多线程程序在调试时,经常会遇到难以复现的BUG,导致调试效率极为低下。因此,一定要给测试环节留出充足的时间,否则就会出现,本来全A的代码,在改了一个CheckStyle错误后,突然红了一个点,改完这个错之后,又有一个点TLE了,然后整个人在风中凌乱的场景,无论怎样,第六次作业大概会给我留下永久不灭的印象。在实际线程同步错误修改的过程中,我也认识到了逻辑的冗杂与高程度的耦合往往会带来灾难性的后果,明白了唯有清晰整洁的架构才能稳定运行不出差错。

  • 第三单元:自动测来一份?

    本单元介绍了通过SMT Solver和Junit4等测试工具对规格化代码进行检查的方法,然而理想永远比现实丰满,自动化单元测试由于JML一些语句的不兼容性,无法正常运行,只能采用Junit对每一个类和方法分别构造测试用例手工测试。但是由于对评测机CPU时间的错误估计,导致第一次作业部分点超时,这也说明了在要求了CPU运行时间上限的情况下,本地进行压力测试是十分必要的。此外,从这个单元开始,已经出现了小规模的同学间对拍测试,在保证正确性方面起到了较大的帮助。

  • 第四单元:一起拍就好了。

    本单元由于要考虑类图、状态图、顺序图的各种情况,在StarUML中手工画各种不同种类的模型图,利用官方包导出,自行构造指令等对程序的功能、原则性检查等方面进行测试。本单元内经常出现的问题极为空指针的异常,这也提醒我们要时刻注意边界条件的处理。此外,与其他同学的对拍测试为本单元作业的调试也带来了极大的便利,此处特别感谢白心宇同学搭建的线上对拍平台。

课程收获

一学期的面向对象课程结束了,我也在这充实的一学期内收获颇丰,主要有以下几点:

  • 代码风格:每一次的代码都要接受代码风格的检查,十多次作业下来这样的风格已然成为了个人的一种习惯,无论是驼峰命名法,还是每行长度上限,亦或是方法的长度上限,都在帮助我们把代码写得简洁、清晰而美观。
  • 编程能力:这学期的每一周的代码练习,极大的增强了我的代码能力与调试能力,能够写出思路更加清晰的代码,更快更准的定位错误的位置。此外,这学期的作业中也将之前仅仅接触过伪代码的一些算法真正的编程实现了出来,也加深了对于这些算法的理解。此外,我也认识到了充分测试的重要性,以及如何去构造全面的测试样例。
  • 架构设计:每一单元的增量式作业布置,也极大的提升了我在最初构建架构时的整体协调能力。从最开始每次作业都要重构,到可以直接沿用以往框架,对整体架构的把握、预先规划以及对问题的抽象能力都得到了很大程度的提升。同时,对于UML的学习也让我能够从一个更加宏观的角度,来看待整体的架构设计。
  • 思想方法:编程时可以从面向对象的角度思考问题,可以从不同实体间的联系来抽象出不同的对象,而不仅仅是站在面向过程的角度直接思考实现细节。

改进建议

  1. 对后续需求做出预告:如果大致知道后续作业的扩展方向,可能在最初架构设计时会更好的为未来的架构留出合适的空间,避免后续作业因为与预想的不太一样而导致被迫重构。
  2. 实验课增加后续反馈:本学期实验课结束之后,也没有得到课上表现的反馈,无论是正确答案还是作答情况都无从得知。希望在以后的课程里可以让实验课较为及时的反馈结果,使实验课更好的服务于理论课程,更好的帮助同学们提升。
  3. 高工增加研讨课与BUG修复:高工同学在部分课程内容上感觉较下一届有所缺失,既没有研讨课来听取其他同学的经验与思想,也缺少BUG修复环节来修复强测中发现的BUG。(不过似乎高工以后的OO都在大二了)
  4. 及时明确作业与测试规则:第四单元的作业需求在指导书中并不明确,同学们的提问也没有及时的摆脱歧义,暖心贴与测试规则的更新时间差了半天,同学们上午加完暖心需求之后下午就又删掉了,等等党的最终胜利。希望在后续课程中能够在开始时就明确作业要求,避免这种情况的发生。

小结

在此感谢课程组所有老师和助教一学期来的辛勤付出,是你们不断的努力才能让这门课程的优质内容以更好的方式不断的带给新的同学们。也感谢身边的同学们,感谢一学期来大家思想方法的交流讨论与互相帮助。希望OO课程在未来能够更加优秀,在北航精品课程的道路上越行越远,成为更多同学的美好回忆。

结束,也是新的开始。OO课程已经结束,但是面向对象的思想却会一直在未来的道路上伴我前行,愿未来的实践中,这些思想能够在看似不以为意的地方,发挥其至关重要的力量。

猜你喜欢

转载自www.cnblogs.com/Kev7/p/ooBlog4.html