面向对象程序设计--第一单元总结

一、前言
说起来,这三次作业做下来,真有点从“搬砖工”到“配载工程师”的感觉(笑)。第一次作业的时候,我还以为只是写个简单的装货程序——输入航班号、几个货物,按重量排个序,装进一个货舱,别超重就行。结果第二次突然冒出两个货舱,还要分前舱后舱,每个舱还有自己的行和列,货物得按重量排序后装进指定的舱。到了第三次更“离谱”,不仅加上了旅客和行李,还要算力矩、重心百分比,甚至要判断飞机是安全还是危险……这大概就是迭代开发的真实写照吧。

从知识点来看,三次作业覆盖了挺多东西:
Java基础语法(类、对象、集合、输入输出)
面向对象设计的单一职责原则(SRP)
组合与聚合关系(比如乘客和行李必须是组合,不能随便改)
手写冒泡排序
输入校验与异常处理
物理公式计算(力矩、重心、MAC百分比)
格式化输出(保留一位小数,对齐格式)

题量方面,每次迭代都要在前一次的基础上改结构、加功能,还不能破坏原有的逻辑,说实话挺考验耐心的。

难度嘛……第一次算热身,只要搞懂类之间的关系,基本能过。第二次开始有点意思了,因为要处理两个货舱的独立容量、排序装载、超载提示,而且输出格式很严格,错一个空格就扣分。第三次难度明显上升,既要加旅客和行李,又得写一个纯静态的计算类,还不能用继承和多态,全靠组合和依赖。尤其是那个重心百分比公式,我第一次写的时候忘了把空机力矩算进去,结果算出来的CG%离谱得很……

总的来说,这三次作业虽然折腾,但做完之后确实感觉自己对“面向对象”这四个字有了更实在的理解。下面我就分别复盘一下每次作业的设计、踩过的坑以及改进的想法。

二、作业回顾

第一次作业:基础航班货运配载

2.1 作业要求回顾
第一次作业其实挺单纯的。大概就是:
输入航班号、最大起飞重量、最大业载重量。
输入几个货物(货物名称、重量)。
系统先把货物按重量从高到低排序(不能用Collections.sort,得自己写排序)。
然后一个一个往货舱里装,如果装了之后总重量超过货舱最大载重,就装不下,给出提示。
最后输出每个货物的装载结果(成功/失败),以及货舱和航班的整体重量状态。

注意,第一次只有一个货舱,没有前后舱之分,也没有位置网格。货舱的容量就是一行输入的那个最大载重量。

我当时理解得挺快的,心想这不就是个“排序+累加”嘛。但实际上手写代码才发现,坑都在细节里。

2.2 我的设计

类图:
屏幕截图 2026-05-17 210319

复杂度分析:

屏幕截图 2026-05-17 193449

2.3 踩过的坑 & BUG分析

2.3.1 坑一:排序方向写反了
这其实是升序(小的往上浮)。结果输出的时候,最重的货物反而最后装,导致我明明先输入一个重的,却被排到后面去了。幸亏我跑了一组测试数据发现顺序不对,赶紧改成 < 才正常。
教训:写排序之前先用几个简单数据手动推一遍,不要想当然。

2.3.2 坑二:输出优化遗漏了“-1x”的系数1
这是互测的时候被发现的。我的程序在输出导数的时候,对于 -1
x 这样的项,我输出的是 -1*x,但按照常规化简,应该输出 -x。我其实知道要优化,但只处理了系数为1的情况,忘了处理系数为-1的情况。
教训:优化的时候要成对考虑正负情况,不能只做一半。

2.4 测试方法

一是对比阅读代码漏洞(即人工审查他人代码逻辑),二是预运行(即提前运行自己的程序验证正确性)

2.5 小结
第一次作业虽然逻辑简单,但对于刚接触面向对象的人来说,还是很容易在细节上翻车的。我最大的收获是:不要小看输入校验和输出格式,PTA对这些要求非常严格,错一个空格、少一个换行都会判错。另外,互测环节让我意识到了“边界条件”的重要性——正常流程谁都能写对,但异常情况才是区分度。

后面第二次作业,我吸取了这些教训,但新的问题又来了……

第二次作业:多货舱管理与重量排序装载

3.1 作业要求回顾

第二次作业在第一次的基础上直接升了一级。主要变化:
飞机不再只有一个货舱,而是前舱和后舱两个货舱,每个舱有独立的ID(“1”和“2”)、最大载重、还有行数和列数(组成位置网格,虽然本次没用到具体格子,但要求先搭好结构)。
输入顺序变了:先输入航班号,然后分别输入前舱的行、列、最大载重,再输后舱的行、列、最大载重。
然后输入乘客人数和每个乘客的行李重量(注意:第二次作业其实还没强制要求旅客?等一下,我翻一下题目……哦对,第二次作业还是纯货物,旅客是第三次才加的。第二次的输入顺序是:航班号、前舱参数、后舱参数、货物总件数、前舱货物件数p,然后p件货物(编号+重量)装前舱,剩下的装后舱。排序是按货物重量从高到低排,然后分别装到指定的舱室。)
输出要求:输出每个货舱已装重量和状态,以及航班整体是否超载。

关键约束:
排序必须手写冒泡排序,不能用 Collections.sort() 和 lambda。
每个货舱独立判断超载,超载要输出特定格式的警告。
输入范围校验:负数或者超出范围要报错退出。

说实话,第一次作业只有一个舱,我还能“偷懒”把逻辑全写在主类里。第二次作业两个舱,还要分别管理各自的货物列表、位置网格,如果不拆类,代码肯定成一锅粥。所以我老老实实按SRP设计了好几个类。

3.2 我的设计
类图:
屏幕截图 2026-05-17 210133

复杂度分析:
屏幕截图 2026-05-17 200754

3.3 踩过的坑 & BUG分析
第二次作业我踩的坑比第一次还多,有些是自己粗心,有些是题目要求理解偏差。

3.3.1 坑一:货舱超载提示格式错(tc2/tc3)
这个问题在PTA上卡了我好久。我的程序运行自己测的数据,输出看起来和题目样例一模一样,但提交后tc2和tc3一直报错。后来我把自己程序的输出重定向到文本文件,和题目给的样例一个字一个字对比,才发现问题。
教训:输出字符串必须和题目描述一模一样,包括括号、空格、标点符号。肉眼不可靠,建议用diff工具。

3.3.2 坑二:前舱货物件数p的范围校验
输入的第8行是“装载在前舱的货物件数p”,要求 0 ≤ p ≤ m(m是总货物件数)。我一开始只校验了 p >= 0,忘了校验 p <= m。结果构造了一个p大于m的测试用例,程序没报错,直接把后面的货物当成后舱的去读,导致数组越界或者读取到错误的数据。
教训:任何从外部输入的数值,只要题目给了范围,就必须校验上下界。

3.4 测试方法
第二次作业我依然用了两种测试方法:预运行 + 人工阅读代码。

3.6 小结
第二次作业让我真正理解了“单一职责”的好处。我把输入校验、排序装载、数据存储分开之后,修改任何一个部分的bug都不会牵连其他部分。比如修正冒泡排序的边界错误,只需要改 LoadDispatcher 一个类,Flight 和 CargoCompartment 完全不用动。

踩坑最多的还是输出格式。PTA对格式的严格程度远超我的预期,差一个空格、一个标点都不行。后面第三次作业我学乖了,先输出到文件,再用 diff 和样例比对,确保一模一样才提交。

另外,这次作业虽然加了 Position 类,但没真正用起来,感觉有点“为设计而设计”。不过按照题目要求做总没错,毕竟迭代开发就是一步步来,现在用不到不代表以后用不到。

接下来就是最复杂的第三次作业——加旅客、行李、力矩、重心百分比……想到就头大。

第三次作业:配平计算(加入旅客和重心

4.1 作业要求回顾
第三次作业可以说是前面两次的“终极形态”。题目在第二次的基础上,新增了旅客实体和载重平衡计算。要求之细、限制之多,让我头大了好几天。

主要新增内容:
旅客管理:新增 Passenger 和 Luggage 类,Passenger 与 Luggage 必须是组合关系——行李对象在乘客构造器内部 new 出来,不能从外部传入或修改。
旅客重量:每位旅客总重量 = 75kg(标准体重) + 行李重量。
力矩计算:飞机有固定的空机重量(40000kg)和空机力臂(16.25m)。旅客舱力臂 18.0m,前货舱(ID=1)力臂 12.0m,后货舱(ID=2)力臂 22.0m。
重心百分比:根据总力矩 / 总重量得到实际重心,再换算为占平均空气动力弦(MAC)的百分比。MAC长度 5.0m,MAC前缘距基准 15.0m。
安全范围:CG% 在 25%~38% 之间为安全(GREEN),否则为危险(RED)。
排序限制:货物排序仍必须用手写冒泡排序,不能用 Collections.sort() 和 lambda。
禁止继承和多态:不能有 extends、interface、instanceof。

输入格式:增加了乘客人数和行李重量列表,货物部分先输入前舱货物件数 p,再分别输入前后舱货物(编号+重量)。顺序和第二次作业略有调整。

另外,输出格式变成了一个非常规整的“载重平衡舱单”,包含基础数据、旅客数据、货物数据、汇总计算、配平评估。所有数值保留一位小数,冒号、空格、等号都要严格对齐。

说实话,看到这个作业要求的时候,我第一反应是“这真的是面向对象作业,不是物理作业吗?”。力矩、重心、MAC,这些概念我查了半天才弄明白。不过一旦理解了公式,代码实现其实还算直接,难的是符合那些严格的类设计约束。

4.2 我的设计

类图:
屏幕截图 2026-05-17 210002

复杂度分析:

屏幕截图 2026-05-17 205635

4.3 踩过的坑 & BUG分析
第三次作业的坑,数量不多但每个都很致命。

4.3.1 坑一:旅客行李构造器理解偏差
题目要求“行李对象必须在 Passenger 构造器内部 new 出来,不对外暴露修改”。我一开始理解成:Passenger 不能有 setLuggage 方法,但可以在构造器外部先 new Luggage 再传给 Passenger。后来重新读题,发现不对——它明确写“行李对象必须在 Passenger 构造器内部 new 出来”,也就是不能从外部传入 Luggage 对象。这意味着 Passenger 的构造器参数只能是行李重量(double),不能是 Luggage 对象。我修改了构造器,内部再 new Luggage(weight)。这是正确做法。
教训:组合关系的“内部创建”和“外部传入”有本质区别,题目要求必须严格遵守。

4.3.2 坑二:力矩计算时漏掉空机力矩
这是我犯的最蠢的一个错误。我按照公式计算了旅客力矩和货舱力矩,然后直接 totalMoment = passengerMoment + cargoMoment,完全忘了加上空机的力矩(空机重量 × 空机力臂)。结果算出来的实际重心明显偏小,导致 CG% 只有 10% 左右,判为危险(RED)。我对着样例数据算了好几遍,才发现空机力矩没加。加上之后,数值就和样例一致了。
教训:物理公式一定要从头到尾列清楚,不能只记住后半段。

4.3.3 坑三:CG% 计算公式中的括号问题
公式是 CG% = ((实际重心 - MAC前缘距离) / MAC长度) × 100%。我一开始写成 (actualCG - MAC_LE_OFFSET) / MAC_LENGTH * 100,运算优先级没问题(乘除同级从左到右),但为了清晰,还是加了括号 ((actualCG - offset) / length) * 100。然而我犯的另一个错误是:实际重心单位是米,MAC前缘距离也是米,相减后除以长度,得到的是小数,再乘以100得百分比。但我有一次写成了 actualCG / MAC_LENGTH * 100 - offset,完全是错的。后来检查才改正。
教训:公式实现后,用已知数据手动验算一遍。样例给的结果是33.0%,可以反推验证。

4.4 测试方法
与前两次一致。

4.5 小结
第三次作业是三次中最难的一次,但也是收获最大的一次。我真正理解了面向对象中“组合关系”的含义,也学会了如何通过工具类(WeightBalanceCalculator)来封装算法逻辑,让主流程变得简洁。

最大的教训是:永远不要相信自己的记忆力。公式、常量、输出格式,一定要反复对照题目描述,甚至直接从题目描述里复制字符串到代码中(比如错误提示语,我就直接复制的)。另外,PTA对输出格式的严格程度,逼着我学会了用 diff 工具逐字符比对,这是个实用技能。

还有就是,虽然题目严禁使用继承和多态,但我反而觉得这种限制让我更专注于组合和依赖,锻炼了另一种设计思路。以后在合适的场景下,我会优先考虑组合而不是继承。

至此,三次作业的总结就全部写完了。回头看,从最简单的单货舱装货,到多货舱管理,再到配平计算,每一步都在往更真实的业务场景靠近。代码量从200行涨到800行,类从5个涨到10个,复杂度也翻了好几倍。但正是这种渐进式的迭代,让我对面向对象设计有了切身的体会。

写完这篇博客,我打算再去看看其他同学的代码,取取经,下次作业争取做得更好。

五、总结
三次作业做下来,回头一看,确实是一步一个坑,但也一步步把“面向对象”这几个字从概念变成了习惯。

最大的收获:我学会了用单一职责的眼光去拆分类。第一次作业我还把输入、排序、输出都塞在 main 里,到了第三次作业,main 就只剩下调用 InputValidator 读数据、创建对象、调 LoadDispatcher 和 WeightBalanceCalculator,清爽很多。组合关系的强制要求也让我明白了“内部创建”和“外部传入”的区别,这比单纯背定义有用得多。

技术上的长进:手写冒泡排序虽然原始,但让我对循环边界和交换逻辑刻骨铭心;力矩和重心计算逼我翻出了初中物理;printf 格式化输出从“随缘”变成了“精确控制”。还有最重要的一点——永远不要相信自己的肉眼,输出格式必须用 diff 工具比对,输入校验必须覆盖所有边界。

需要继续学习的地方:这次作业禁止了继承和多态,但我很清楚在更复杂的系统里,这两者不可或缺。另外,我的代码里还有很多“魔法数字”(比如直接写 16.25、12.0),应该用常量定义;异常处理全是 System.exit(0),不够优雅,后续要改成抛异常或返回错误码。

posted @ 2026-05-18 13:12  陈俊谚  阅读(7)  评论(0)    收藏  举报