面向对象程序设计作业题目集1-3总结

一、前言

这三次PTA作业挺有意思的,都是围绕“航空器配载与货运管理”这个场景,每次都在上次的基础上加新功能,像滚雪球一样越滚越大。从最简单的货物装货判断,到最后算重心、评估安全,难度一阶一阶往上走。我把自己三次作业的知识点、代码量、踩坑情况整理了一下,先列个表让大家一眼看清楚。

作业次序   核心知识点   代码行数   难度评估   主要挑战
第一次 类封装、选择排序、ArrayList、格式化输出   58   较简单 第一次试着用两个类协作,排序逻辑得自己敲,不能直接调方法
第二次 多类协作、冒泡排序、货舱容量管理、输入次序保证、基础校验   202   中等 类突然变多了,责任怎么分?货物分舱的时候差点搞乱
第三次 旅客与行李管理、重心及力矩计算、输入边界校验、静态常量设计、手工排序   373   较难 公式理解错一点全盘错,还得自己写排序,debug到头大

二、设计与分析

第一次作业

第一次任务不复杂:读入航班号、最大载重、货物列表,对货物按重量降序排个序,然后输出,最后判断总重有没有超限。

类图结构:

屏幕截图 2026-05-17 140845

Main里面把输入、排序、输出全包了,Cargo就是个纯数据袋子,只有两个字段和getter。这种写法在功能简单的时候没啥毛病,代码一眼看穿。但我当时已经隐隐觉得不对——要是以后再加点需求,Main不得炸了?果然后面两次作业我基本是推倒重来。

SourceMonitor 指标分析

屏幕截图 2026-05-17 135644

指标   数值   解读
语句数   40 量不大,逻辑一眼望到头
分支语句占比   17.5% 主要就是if判断超不超载,还有循环里的分支
最复杂方法   Main() 复杂度8,对第一次作业来说还行,但已经偏高了
最大嵌套深度   5 出现在排序的双重循环里,循环套循环再套if,看着就晕
平均方法复杂度   2.75 被Cargo那几个简单方法拉下去了,实际上main不低

心得:第一次作业让我明白了一个道理:别把所有代码都塞main里。虽然当时为了赶着通过测试没重构,但心里已经知道这结构撑不了多久。果然第二次就不得不重写。

第二次作业

第二次作业加了多货舱。每个货物有个目标货舱代号,先按重量降序(同重量按输入顺序升序)排好,然后挨个往对应货舱里装,如果装不下就报失败,最后打印每个货舱的装载情况和整机状态。

类图结构

屏幕截图 2026-05-17 140908

类数量一下子加到7个。看起来责任分开了:Airplane管多个Bay,Dispatcher提供排序和查找,Checker做边界检查。但实际写起来还是有点别扭——Dispatcher.sortCargos是静态的,直接改传入的List;Goods里还存着targetId,Main里面一个循环就把它塞给对应的Bay。本质上还是线性思维,没有真的让对象之间互相“发消息”。

SourceMonitor 指标分析

屏幕截图 2026-05-17 135806

指标   数值   对比第一次作业的变化
语句数   154 翻了快4倍,复杂度明显上去了
分支语句占比   13.6% 占比降了,但绝对分支数反而多了
最复杂方法   Main() 复杂度13,比第一次的8还高5个点
最大嵌套深度   4 比第一次降了点,说明循环结构优化了一些
平均复杂度   2.04 getter/setter多了,平均值被拉低
方法/类   3,71 每个类平均3-4个方法,粒度还可以

心得:第二次作业让我发现“静态方法用多了”其实挺害人的。Dispatcher里面全是静态,好像很方便,但这样对象自己反而啥也不干了。理想的情况应该是Airplane自己有个distributeGoods方法,内部去调用各个Bay的load,而不是main在外面一个一个塞。另外,虽然写了Checker类,但实际输入的时候压根没用上——这也给第三次作业埋了雷。

第三次作业

第三次作业加了旅客(含行李)、重心百分比计算、严格的输入校验。题目变成了:给一个航班,前后货舱容量、旅客行李重量、货物清单(分前后舱),算出起飞总重、总力矩、重心位置(%MAC),然后判断安全不安全。

类图结构

屏幕截图 2026-05-17 174719

WeightBalanceCalculator把重心计算有关的常量和逻辑都包在一起,这个设计方向我是挺喜欢的,高度聚合。InputValidator也弥补了上次输入不校验的问题。

SourceMonitor 指标分析

屏幕截图 2026-05-17 135856

指标   数值   对比第二次作业的变化
语句数   260 继续增长,新增旅客和重心计算模块
带注释的行占比   1.1% 几乎没有注释,这是一个严重问题
最复杂方法   generateLoadSheet 复杂度14,比第二次的main(13)还高
最大嵌套深度   6 出现在货舱货物遍历和输出格式化的嵌套循环中
平均方法复杂度   2.3 略微上升
方法/类   3 每个类方法数较第二次下降,说明有些类方法较少

最复杂方法的问题:generateLoadSheet干了太多事——算旅客总重、算货物总重、算力矩、输出各种表格、评估安全。它不应该这么“胖”。按照单一职责原则,至少应该拆成5-6个小方法。

最大嵌套深度6:看看我当时写的代码(类似下面这样)——

屏幕截图 2026-05-17 221455

功能是实现了,但读起来真费劲。而且把货舱类型(id==1或2)硬编码在里面,以后要加第三个货舱,代码就得改得乱七八糟。

心得:第三次作业让我发现,当一开始没留好扩展点的时候,后面加功能就只能“打补丁”。generateLoadSheet就是典型的补丁式产物。还有InputValidator里直接System.exit(0),虽然对PTA这种判题环境能凑合,但实际项目中这么干是不行的。

三、采坑心得

在完成这三次作业的过程中,我遇到了不少具体的问题,有些已经解决,有些至今仍有遗憾。

3.1 第一次作业的坑:

1.比较浮点数时直接使用 ==

判断超载的时候我一开始写的是if (total > maxWeight)。后来想起浮点数精度的问题,改成了if (total > maxWeight + 1e-9)。虽然这个改不改当时测试都能过,但至少让我记住了:浮点数别直接用==或者>,要给点容差。后面两次作业我都统一用了1e-9。

3.2 第二次作业的坑:

1.冒泡排序的交换条件写反了

第二次作业要求“同等重量时按输入顺序升序排列”,我在实现冒泡排序时,交换条件的逻辑写成了:

屏幕截图 2026-05-17 222837

但在实际冒泡中,为了达到降序效果,应该是“如果前一个重量小于后一个重量”才交换。这个逻辑本身没有问题,但因为我在循环边界和索引处理上不够仔细,导致排序结果偶尔出现乱序。最后通过打印中间过程才发现问题:原来是在多重条件分支中少考虑了一种情况——当重量相等且输入顺序相等时,不应该交换,但我的条件分支没有显式处理,导致在某些边界条件下 保留了上一次循环的值。修复方法是每次循环前重置 。

3.3 第三次作业的坑:

1.重心百分比计算公式理解有偏差

WeightBalanceCalculator 中重心的计算方式为:

屏幕截图 2026-05-17 223618

我一直以为cgPercent算出来就应该在安全范围[25,38]内,但怎么算都不对,老是超。折腾了大半天才发现:力矩累加那块出错了——我把ARM_FRONT用到了所有货舱,实际上后货舱应该用ARM_REAR。改完之后,cgPercent就正常了。这种错就属于“抄公式抄错变量”的低级失误。

2.排序的陷阱

generateLoadSheet 方法中注释掉了一段代码:

屏幕截图 2026-05-17 223844

这本来是要对每个货舱内的货物再按重量排序,结果我注释掉之后忘了恢复。导致输出货舱货物清单时,顺序是输入顺序而不是重量降序,题目要求的那个测试点直接凉了。现在想想,这种错完全是自己粗心。

3.4 输入校验的“死板退出”

InputValidator里面,只要输入不合法就System.exit(0)。在PTA上能过,因为判题系统只关心输出对不对,不关心程序有没有粗暴退出。但真写项目的话,这绝对是坏习惯。更好的做法是抛异常或者返回Optional,让上层决定怎么处理。这一点我以后得改。

四、改进建议

如果让我重新做这三次作业,或者再迭代一版,我会在下面几个地方下功夫:

4.1 第一次作业——把排序抽出来

即使是小作业,也可以养成好习惯:单独写一个CargoSorter类,main只负责调用。以后想换排序算法,只改CargoSorter就行,main不受影响。

4.2 第二次作业——用策略模式代替静态排序

Dispatcher.sortCargos静态方法写死了冒泡排序。更好的设计是定义一个SortStrategy接口,然后让WeightDescSorter去实现它。以后如果需求变成按货物名排序,再加一个NameSorter就行,不用改原有代码——这就是开闭原则。

另外,货物分配的逻辑不要放在main里。可以在Airplane里加一个distributeGoods(List<Goods> goodsList)方法,让它自己去叫每个Bay的load。main只负责创建对象和调用,不负责具体的分配细节。

4.3 第三次作业——拆分generateLoadSheet

这个方法复杂度14,已经高得离谱了。可以拆成:

      1.calculateTotalWeight()

      2.calculateTotalMoment()

      3.calculateCG()

      4.printPassengerInfo()

      5.printCargoInfo()

      6.printBalanceAssessment()

拆完之后每个方法都很短,容易测试,也容易看懂。那些常量(WEIGHT_EMPTY、ARM_PAX等)最好挪到一个单独的AircraftConfig类里,以后换机型只需要改配置类。

4.4 通用改进——写单元测试

三次作业我全是用手工测试+提交PTA看结果。效率极低,而且很多边界条件根本测不到。以后应该用JUnit给核心方法写单元测试,比如测试Bay.load在超载时是不是返回false,测试WeightBalanceCalculator在各种合法/非法输入下是不是算得对。养成TDD的习惯,虽然开始慢,但后期省时间。

五、总结

通过这三轮迭代,我觉得自己在下面几个方面进步最明显:

      1。面向对象意识:从第一次的main一条龙,到第三次知道要分好几个类,虽然分得还不完美,但至少开始考虑职责分离了。

      2.排序算法:自己亲手实现了选择排序和冒泡排序,还处理了多级比较(先比重量、再比顺序),对稳定排序的理解加深了。

      3.浮点数处理:被精度坑过一次之后,再也不敢直接用==了,容差比较成了习惯。

      4.代码复杂度敏感度:看了SourceMonitor的报表之后,我意识到一个方法复杂度14意味着什么——不只是维护困难,改起来也容易引出新bug。

还需要继续学的东西:

  1. 设计模式。尤其策略模式、工厂模式,可以让代码更容易扩展。

  2. 异常处理和输入校验的优雅写法。别再System.exit(0)了。

  3. 单元测试。必须养成写测试的习惯,不能再靠手工点。

  4. 类图设计。写代码之前先画类图,想清楚类之间的关系,能省很多返工时间。

对课程的小建议:

      1.可以在下一次迭代开始前给一个参考类图或设计思路,帮我们避免一些明显的结构坑(比如第三次作业里我注释掉的那段排序代码,如果有提示就会注意到)。

      2.测试点里多加点非法输入的边界场景,比如货舱容量是0、货物重量是负数之类的,倒逼我们认真做好输入校验。

      3.能不能在作业要求里明确加上“必须提供类图和复杂度分析截图”?这样大家提交之前就会主动审视自己的代码质量,而不只是盲目追求测试点全绿。

最后想说,三次作业虽然过程磕磕绊绊,第三次还没拿满分,但我真的体会到了“迭代开发”的味道——没有一次设计是完美的,都是在后续不断重构、不断踩坑、不断优化中变好的。以后写稍微复杂一点的程序,我会先问自己三个问题:这个类的责任单一吗?最复杂的方法能控制在10以内吗?加一个新功能要改多少旧代码?这三个问题,就是我这三次作业学到的最实在的东西。

posted @ 2026-05-17 23:08  横行的夜雨  阅读(14)  评论(0)    收藏  举报