BUAA OO Unit4总结

写在前面

忙碌了一个学期,我们终于走到了OO长征胜利的那个终点,回望整个学期的学习过程不禁唏嘘。

感慨还是放在结尾部分吧,先来总体讲讲Unit4这最后一单元的内容。本单元我们学习了UML图的绘制,三次作业覆盖了对类图、状态图和顺序图的考察。不过没想到的是,作业的整体难度并不在UML图的绘制,而是在代码部分。

在参考了往届第四单元的作业后,我长出一口气,心想终于可以写一点轻松愉快的OO,专心备考了。然而让我没有想到的是,第十四次作业爆了大锅,彼时恰逢OS考期,焦头烂额的程度让我和身边的同学都感到无比焦虑,讨论区和微信区的激烈反响也体现了这次作业出了不小的问题,ddl一拖再拖,因此第十五次作业也顺延到了考期之中,复习的时间十分紧迫。

但是,我认为教学组的应对方案是合理并且恰当的,在此感谢助教团队的日夜付出,维护了课程的教学目的,也体现了OO课程人性化的教学方案;感谢吴际老师随时关注并回应同学诉求的负责态度和耐心友善的沟通交流,我们才能在崎岖的路上步步前进。课程改革的过程中总有试错的过程,重要的是及时的纠正和调整,我衷心祝愿OO课能在未来变得更好,感谢你们的付出!

正向建模与开发

在面向对象编程中,正向建模与开发是指从需求分析到系统实现的一种开发方法。它强调从问题域的角度出发,通过建立模型来描述系统的结构、行为和关系,并将这些模型转化为可执行的代码。

在本单元的作业中,这个模型是UML图。通过三次作业,我们建立了类图、状态图、顺序图三种正向开发的模型,并以此为依据进行程序的开发设计。在三次作业中,为了梳理复杂的图书馆逻辑,我进行了三次类图的绘制和修改,并根据要求绘制了相应的状态图和顺序图。由于整个图书馆系统的业务流程十分复杂,因此我们需要实现与其相关的类图架构,并依据绘制的类图来实现相应的功能。

利用UML进行的正向建模与开发对于编写复杂流程程序的帮助是显著的。类似于作文提纲,好的提纲能带来优质的内容,编程也不例外。

下面是本单元最终的类图:
第十五次作业类图
可以看到,类与类之间的关联关系相当复杂,因此如果不采用正向建模的开发方式,很有可能出现思维纰漏的地方,因此产生难以发现的bug。

第十三次作业

作业分为两部分,绘制UML类图和编写程序代码。

在读懂题目后,需要将整个小型图书馆系统的操作步骤进行细化分配,主要分为以下几个步骤:对输入的处理、对请求的相应并作出判断、输出响应请求的反馈信息。分析后的第一反应,我初步只设计了三个类MainLibraryOrder,我觉得这三个类已经足以完成我需要的图书馆系统。直到设计好UML类图、编写完代码后,代码风格测试告诉我Library类太过冗长,我才将对请求的解析放入了Parser类、将反馈信息的输出放在了Printer类。尽管如此,我的类还是过于聚合,这为我十四次作业的重构埋下了祸根。事实证明,这个架构烂到不能再烂,在后续作业增量迭代时,我几乎无从下手,只得推翻重来。

在代码方面,我认为整个题目的难点体现在每隔三天进行一次的整理。如何处理整理?根据输入请求的日期判断是否整理?采用特殊的计数器进行特判?通过一个上午的思考和尝试,我意识到评测并不针对时间戳进行,因此可以将整个流程看作一次模拟,假设自己处在[2024-01-01],所有的请求都已经被接收并且存储在一个请求容器中,这时我们再根据请求的类型,进行日期自增的模拟,在当前的模拟日期和请求日期相同时,按照输入请求的顺序处理请求,以此来达到相同的输出目的。

需要注意的就是在date自增时需要更新随天数变化的数据,难度并不算大。

对于请求的处理也并不复杂,总体分为三种:借出、损毁和丢失。其中最简单的是丢失,只需要在丢失的瞬间进行罚款即可。借出的时候需要判断是当场借出、拒绝借出还是登记预约。损毁需要记录,在还书时执行还书流程并增添修书流程。由此可见对请求处理的流程并不复杂,需要处理的就是如何通过输入的请求和相应的对策构建输出。分类讨论即可解决所有请求的分支。

由于这次作业均出现在一个图书馆内,因此将对所有请求的处理放在Library类中是一种很方便的设计方案,因为它不涉及跨类访问,但缺点就是类的职责变得不够明确(😭)。

另外,对于整理管理员和预定管理员这个两个管理员,我认为他们都可以在代码中优化掉。整理管理员在整理日到来时将各个部门的书籍收集起来,将预定的书籍分发给预定管理员,再将剩余的书籍放上货架。我将这个流程简化如下:

在整理日到来时,将所有部门的书籍统一移动到书架上,先让预定队列访问书架,如果有可以满足的预约就将书取走。等遍历了所有预定后,再开始处理新一天的所有请求。这个思路我个人认为还是很有创新性的,至少身边的朋友都没有这样简化的。

从马后炮的角度来说,这次作业值得细讲的东西并不多,注意维护好图书可能出现的每个部门的数据即可。当时看到作业时感觉很复杂很困难,但实际上只用了不到一天的时间就完成了类图的设计和代码的编写,和第十四次作业相比起来,这次作业实在是太过幸福了。

最终版本代码行数:592(+592)

第十四次作业

时至今日,我还是难以忘记这次作业带给我幼小心灵的极大冲击。整个指导书长度生平仅见,流程繁琐程度史无前例,前后逻辑要求矛盾百出,最主要的是当次作业与OS期末考试完美撞车…于是这次作业堪称一次对我的浩劫。

与第十三次作业相反,看到题目,我的第一反应是简单,校际借阅也不怎么难处理,把上次作业的Library类略加修改就行了,于是也没有太在意。知道考完OS,周六上午开始上手实践时,才发现事情没有我想象的那么简单。

从大的框架来看,依旧可以沿用第十三次作业实现的大模拟系统,只是模拟发生的位置需要由单个图书馆变为整体的调度器Runner类。

其次,优化整体流程的方法仍然可以沿袭,这次新增的图书管理处也可以直接优化掉,思路和第十三次的思路几乎一致。

支持校级借阅后,需要维护的数据激增,并且处理逻辑也变得十分复杂。比如,如果校际借阅的图书遭到了损毁或丢失,需要通过一个调度器对图书所属的学校的相关数据进行维护。另外,图书的加购是一个复杂的流程,需要经过层层判断才能决定究竟是否需要进行加购操作。这次作业需要注意的细节实在太多,下面我一一分析。

首先需要明确,整个借阅流程增加了校际借阅的选项,总体的借阅行为如下:

申请借阅->校内借阅->校际借阅->校内预定(如果需要则加购图书)。我将以这个流程为框架来分析各个步骤的行为。

校内借阅

与上次作业的限制没有太大区别,如果当前校内书架上有这本书,那么校内借阅就可以被接受,如果校内借阅被拒绝则进行下一步。

校际借阅

在当日的所有请求被处理完后,判断所有非自身原因导致的被拒绝的校内请求是否可以进行校际借阅,判断依据为闭馆时外校书架上是否有这本书,这是第一个需要特殊处理的情况,即所有校内的请求先被处理,无论校际借阅的请求是否先于校内借阅请求,都优先处理校内请求,再去尝试满足校际请求。

为了实现先处理校内请求,再处理校际请求,可以定义一个DelayRequest类,用来专门存放所有未被立即处理的请求(包括当场借出和当场拒绝),在日期即将自增时,再处理这些delayRequest。需要注意的是,DelayRequest对象只能创建一个,其数据和操作由调度器和所有图书馆共享。

校际借阅涉及了一个本次作业最难处理的难点:书籍的运输。之所以说它最难处理,是因为它包括了很多种不同的情况,涉及了很多数据的维护。

我们需要设计一个TransferMap类来存储所有的校际借阅信息。

//省略了部分功能性容器,只展示所有的数据性容器
private final HashMap<String, HashMap<String, String>> transferMap;
private final HashMap<String, String> interBorrowBMap;
private final HashMap<String, ArrayList<String>> interBorrowCMap;

public TransferMap() {
    this.transferMap = new HashMap<>();
    this.interBorrowBMap = new HashMap<>();
    this.interBorrowCMap = new HashMap<>();
}

其中,transferMap以书号为索引,书的借出学校名和借入学校名成对的哈希表为键值。采用这样的结构是考虑到校际借阅可能会出现不同学校的学生对某本书的借阅,借入学校的值可以为复数,因此需要不同的哈希表来存储成对的借阅关系,此表的作用在于方便打印反馈信息;interBorrowBMapinterBorrowCMap以学生id为索引,其所借外校图书的书号为键值,记录了不同学生申请的校际借阅,方便在还书时进行分支判断处理。

由此,相信你也可以感受到这样的结构带来的维护困难度是十分高的。只要涉及校际借阅,这三个表的维护都需要去考虑。

校内预定

如果校际借阅的条件仍然不满足,那么就需要走校内预定流程。预定的细节与第十三次作业大致相同,不过需要注意,预定的发生时间点在判断完校际借阅之后,这意味着预定接受的时间点也由发出借阅请求当场变为了每天闭馆之后。(吐槽一下,如果没能当场借到书就得在图书馆这里等一天,感觉有点怪)

这里还需要声明:一旦进行了校内预定,那么这个请求和校际就没有任何关系了,该借阅请求只能通过校内借阅完成。这实际上大大减少了流程的复杂度。

图书加购

如果发现校内图书馆馆藏压根就没有这本书,就需要记载加购信息。加购图书一律在整理日进行,并且以图书馆为单位进行加购。为此,我们仍然需要创建一个存储加购信息的容器,在整理日到来的时候访问它,完成加购。加购的行为可以简化为将图书直接放在书架上,以供直接借阅。另外还需要维护图书馆的历史馆藏数据。

至此,基本的借阅思路我们已经理清。不难发现,请求可以分为直接处理请求和延迟处理请求两大类,对延迟处理请求的处理可以进行进一步的分支。实际上,如果将整个逻辑处理清楚,会发现也并不算太繁琐。真正繁琐的是细节处理,比如这里访问越界了,那里忘了维护数据等等,诸如此类的问题接踵而至,让人难以招架。

此次图测评增加了对图书的状态图检测,主要需要我们自己设计状态迁移名称,并且在输出中增加设计性输出。难度不大,注意GuardTrigger的写法即可。

由于在第十三次作业中采取了不够便捷的架构设计,此次作业除了图书馆内部处理请求的逻辑没有太大变动之外,其他的程序都经历了几乎重写的重构,总共用时三天才成功拿下中测。

最终版本代码行数:1640(+1048)

第十五次作业

拿到作业要求,我还以为自己看错了。不知是考虑到考期将至,课程组为我们考虑,还是因为任务体系没有构建完毕,总之这次作业的难度远远在意料之外。因为它太简单了。

我们只需要增添一个Counter类作为计数器,用来保存借书的日期即可。具体的构建方法如下所示。

用法也相当简单,在借出书的时候(注意是成功借出而不是接收到借书请求)调用addNew方法,在还书的时候调用checkDate方法。

本次作业的代码迭代部分我用时不到半小时就完成了,实在是令我震惊。

最终版本代码行数:1778(+138)

UML图与程序的追踪关系

正如上文所提到,第十三次作业中,我没有重视绘制UML图的重要性,于是草草架构了事。结果就因为类的划分不够细致,导致了我在第十四次迭代中几乎完全推翻重做。到了第十四次时我开始小心谨慎地设计UML架构,在完成了设计后再着手代码编写,并且发现了某些功能缺失后返回类图中添加项,因此UML图与程序之间是一个正向的相互完善的作用。

具体而言,在本单元我设计程序的流程是:绘制UML图->根据UML图编写代码->发现UML图功能不完善并改进->反馈到代码中实现

学习心得

通过本单元的学习,我系统地学习了UML三种图的画法,即类图,状态图,顺序图。通过预先绘制UML图,可以帮助我在编写程序前理清思路,做好架构,在开始编程的时候能有十分清晰的逻辑性,提高了自己编程的效率。

和Unit3不同,本单元的算法要求基本是没有的,重点在于理解整个图书馆系统的运转流程,无论是单图书馆系统的借书流程还是多图书馆系统的协同借阅,都需要对程序的运行流程有一个非常清晰的认识(虽然暴雷的某次作业实在是不忍直视),这也正是强调了UML绘制的重要性,起到了不错的训练效果。

四个单元的架构设计思维

第一单元重点考察了对输入多项式的解析。实际上对于第一单元,我的了解不如后续充分,而只是简单地使用了训练中给出的字符解析架构去尝试编写自己的程序。大致的思路是有的,和别人的思路相差也不是很大,但由于对Java语法的不熟悉和对OO课流程的不了解,我没能成功完成第一单元,所以就不再赘述。

第二单元是多线程程序。这是最复杂、难度最大的一个单元。本单元的第一次作业,我积极地学习别人的博客,尝试在他们的架构基础上完成自己的设计。虽然成功了,但并不是我自己独立完成的设计,而是依赖于他人。于是在第二次和第三次的迭代中,我尝试自己去添加相应的功能(实际上也没有博客可供借鉴了),但效果并不是很好。此时,我的架构设计思维还没有达到要求的水平,因此设计尝试是比较失败的。

第三单元主要学习了JML规格。从这单元开始,作业的难度已经不像前两个单元那么高,于是我开始尝试自己进行设计。由于JML规格的特点,因此架构上要求不高,我们只需要按照规格给出的要求进行设计代码就可以了。反而难度出现在算法相关的问题上,不过这和架构设计思维没有太大的关系。总体而言,通过学习给出的JML规格,第三单元让我完全了解了架构设计的整体思路,以及在迭代过程中需要遵守的设计原则。

第四单元是UML图与程序的结合练习。在最后一个单元,我对之前训练的架构设计思维进行了完整的运用,从UML图的绘制到程序的迭代开发,我深刻体会到了架构设计的重要性,并在第一次迭代暴雷后有了更加谨慎的迭代思路。

总体而言,虽然没能完整地完成课程的训练,不过在四个单元的学习中,我还是掌握了一些关于架构设计的技巧,这对于我今后的开发和设计有举足轻重的作用。

测试思维的演进

在刚开始测试时,我只会将样例复制到标准输入进行测试,一旦输出和样例中给出的一致,就提交代码不再测试。这是我从大一以来养成的不好的习惯,因为无论是程序设计还是数据结构,提交测试是没有时间代价和次数代价的,我完全可以根据返回的测试结果进行修复。但长久之下,OO课这样做是行不通的。于是我慢慢开始自己手搓一些我认为可能有坑点的数据,也偶尔能够发现一些自己程序的漏洞。

从第二单元第二次作业开始,我开始依靠同学分享的评测机进行自动化评测,这也是我上大学以来第一次接触自动化评测手段,顿时感到这样的测试方法十分高效。除此之外,我也和一些同学进行了对拍,将一些有争议的数据进行了更正。

OO课告诉了我,编写一个程序,完成编写绝对不是工作的全部,甚至完成编写才是工作的开始。在很多时候,修复代码中的bug需要花费的时间远比编程本身要多。对于某个程序而言,尽善尽美才是我们应该去追求的。

对课程的建议

对Unit4的建议

不难发现,我们这届的Unit4任务和往年相比有了翻天覆地的变化,可以看到课程组有在进行Unit4训练的改革,但由于种种原因,我们是可以体感到执行效果并不是很好。第十三次作业的难度和任务量我个人认为是合理的,但是第十四次作业任务太重、流程太复杂,而第十五次作业的难度和复杂度又是史无前例的低,

我个人认为,综合来讲,整个Unit4的任务量处在一个较低的水平,只是在本届试运行时没有控制好每次作业应该完成的量,进而导致了某次作业的负担过大,而某次作业的任务又太少。以第十四次作业举例,增加校际借阅的任务量已经足够,而加购新书的任务完全可以放在第十五次作业进行迭代。另外,看的出来课程组其实在原本的计划中还有别的要求,例如增加多个管理员和部门等,但由于安排问题没能实践,这是一个试错调整的过程,可以在后几届的运行中慢慢调整。

总而言之,相比于往届的Unit4,这次革新我认为是比较成功的,训练的方向也比较正确,难度也适宜。希望之后课程组可以完善Unit4的任务安排和计划,早日将完整的Unit4呈现给学弟学妹们。

对课程整体的建议

本来我想先从理论课和实践课的结合程度来谈谈,希望二者能更加紧密一些。不过考虑到其他编程课的普遍情况,意识到不好改变,于是决定换个角度。

首先,作为上个学期没有抢到OOPre课程的一批倒霉的学生,在这学期开始时度过了怀疑人生的一个月。就我自己而言,整整Unit1三次作业,我连一个有效作业都没有,而是需要利用这一个月的时间去好好学习Java的相关知识。现在马后炮地想,如果第一单元的作业可以补交,一个星期之内我一定能写完那三次作业,而不至于现在还纠结于能不能及格的问题。。。所以第一个建议,如果OOPre的重要性真的如现在一样高,那为什么不把它作为一个置课,让每个学生平等地进行先导知识的学习,反而需要通过抽签抢课的拼运气方法来选课呢。希望课程组能考虑我的诉求。

其次,单元之间的安排是有问题的。我之前就提到过,Unit3整个单元相对来讲有些冗余,并且其难度并不是很好控制。因此我建议,要么将Unit3的学习内容进行调整,要么将Unit3放在Unit1之前进行,以便控制Unit3的难度。按照常理,单元之间的难度应该是一个循序渐进的过程,所以最理想的安排方法应该是Unit3->Unit4->Unit1->Unit2,不过考虑到学期结尾时的繁杂之事较多,Unit2的情况可能会很惨,所以我主张课程组将训练顺序调整为Unit3->Unit1->Unit2->Unit4

讲给OO

OO课应该是我目前上大学以来情感最复杂的课程,我对它又爱又恨。爱,是因为每当自己完成一周的作业时,总会有一种会当凌绝顶的自豪感,每次都是如此,永远是新鲜的体验。恨,是因为它占据了我大二下学期生活的几乎全部,我无时无刻不在思考OO的事情,无论是没能写出来的Unit1,还是后续的几个Unit,OO总是能每分每秒地占据我的大脑。

自从进入大学以来,我便一直都能感受到自己的编程水平远远不够。大一时的程设和数据结构,我的成绩都不理想,因此我一度非常怀疑自己能否顺利完成计算机学院的高水平要求。而祸不单行,我没有选上先导课,这对我而言是一个沉重的打击。在学期伊始,我一度担心自己不能完成OO的学习而考虑退课。不过,最终我还是完成了它。

OO是艰苦的,因为我忘不了那些伴着舍友的呼噜声挑灯夜战的日子;OO是残酷的,因为我知道,如果我稍不努力,就很可能掉入挂科的万丈深渊。直到今天,我终于走完了这段旅程,既不像金身大佬那样有无懈可击的辉煌,也不像编程高手那样巧思频出,受人膜拜。我只是尽力做好了自己,安静而努力地走完了这段我应走的路。也许最终的结果无法让我感到满意,感到释怀,但我仍会因为能够亲自走过这段艰难而充实的路,为自己欢呼呐喊。

感谢OO课程让我能够直面曾经的软弱,克服内心对编程的恐惧;感谢助教无微不至的教导,贴心的答疑,让我能够鼓起勇气,继续前进;感谢同学,能够在我思维陷入僵局时为我提供帮助。

最后,希望OO课程能够越来越好,最好的祝福,送给这门我爱之痛之的课程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值