面向对象程序设计集一到三总结

一、开篇:三次作业到底让我经历了什么?
先放一张图吧(脑补一下):第一次作业,我还在纠结“货物怎么按重量排序”;第二次作业,我开始头疼“前舱后舱不能混装”;到了第三次,我直接面对“重心百分比MAC”这种航空工程术语……三次作业,就像从“搬砖”到“造飞机”的进化史。
知识点上,从最基础的类封装、ArrayList使用,到组合/聚合关系、单一职责原则(SRP),再到力矩计算、输入校验、冒泡排序手写,可以说把Java OOP的核心概念串了一遍。题量方面,第一次不到150行,第二次突破200行,第三次接近300行,难度也从“照着样例写”升级到“必须理解物理模型”。
说实话,前两次我还能靠“暴力调试”混过去,第三次如果不理解力臂和重心公式,代码写出来也是一团乱麻。下面我按作业顺序,逐一回顾我的设计思路、踩过的坑,以及最后怎么改对的。

二、第一次作业:单货舱货运配载——从“大而全”到“各司其职”
2.1 我最初的设计(反面教材)
拿到题目时,我的第一反应是:“简单!一个Main类搞定一切。”于是我把航班号、最大载重、货物列表全写在main方法里,输入、排序、输出、超载判断全部塞在一块。代码倒是能跑,但一旦想修改输出格式,整个逻辑都要动。老师强调的SRP,我根本没当回事。
结果在第一次提交后,被同学提醒“你的Cargo和Flight类呢?”我才意识到,我连最基本的类都没分。于是硬着头皮拆出了三个类:
• Cargo:存货物名称和重量,提供getter。
• Flight:存航班号、最大载重。
• CargoLoader:负责添加货物、排序、计算总重、输出报告。
2.2 排序的“偷懒”与代价
第一次作业没有禁止使用Collections.sort(),我自然是直接用了lambda一行搞定。当时觉得“真香”,完全没想过后面作业会强制手写冒泡。结果到第三次作业时,我发现自己对冒泡排序的稳定性和边界条件几乎一片空白,花了一晚上才调对。这个教训让我明白:依赖高级API没问题,但底层原理必须懂。
2.3 核心代码片段(第一次最终版)
Collections.sort(cargoList, (a, b) -> Double.compare(b.getWeight(), a.getWeight()));
double total = 0;
for (Cargo c : cargoList) {
total += c.getWeight();
System.out.printf("货物[%s 重量:%.1fkg]%n", c.getName(), c.getWeight());
}
System.out.printf("总重量: %.1fkg / 最大载重: %.1fkg%n", total, flight.getMaxWeight());
System.out.println(total > flight.getMaxWeight() ? "配载状态:严重超载" : "配载状态:正常");
输出格式严格按照题目要求,保留一位小数。这个阶段我最大的收获是:一个类只做一件事,真的能省很多事。

oop1

三、第二次作业:多货舱管理——组合与聚合的实战演练
3.1 新增Position类和CargoCompartment类
第二次作业要求区分前舱和后舱,每个舱有独立的最大载重,还要有位置网格(行列)。我设计了:
• Position:只存行、列,提供getPosName()。
• CargoCompartment:包含id(“1”或“2”)、maxWeight、一个List(组合关系,构造函数里new出来)、一个List(聚合关系,Cargo独立存在)。
这里我纠结了很久:为什么Position是组合而Cargo是聚合? 后来想通了:货舱没了,那些位置格子自然也不存在了,但货物卸下来后还是货物。代码里就体现为:positions在构造时生成,对外只读;cargos则可以独立添加/移除。
3.2 货物排序与装载的顺序问题
题目要求:所有货物先按重量从高到低排序,同等重量按输入顺序(稳定排序),然后依次尝试装入指定的货舱。如果目标货舱容量不足,立即报错并停止程序。
这里我犯了一个经典错误:先把前舱货物和后舱货物分开排序,再分别装载。结果导致全局顺序被打乱(比如一件很重的前舱货物可能排在一件中等重量的后舱货物后面)。修正后,我把所有货物放进同一个List,整体排序,再根据targetCompartmentId去找对应的CargoCompartment。
3.3 全局超载判断的两个维度
最终要输出:
• 每个货舱的已装重量/最大载重 → 状态(正常/超载)
• 航班总重量 vs 最大起飞重量 vs 最大业载重量 → 四种情况组合
我把判断逻辑写在了CargoLoader里,计算所有货舱重量之和后,分别比较两个阈值,用if-else链输出对应提示。这里踩坑点是:题目要求两个警告可能同时出现,不能只判断一个就else。我最初写成了if(超起飞) 警告A; else if(超业载) 警告B; else 正常;,结果两个都超时只输出第一个。改成了分别标记两个布尔值,最后组合输出。

oop2

四、第三次作业:重心平衡计算——真正触及航空物理
4.1 新增Passenger与Luggage:组合关系的典范
这次新增了旅客,每个旅客有标准体重75kg + 行李重量。题目要求:Passenger内部new一个Luggage对象,不对外暴露修改。我一开始图省事,直接在Passenger里写了一个double luggageWeight字段,被助教扣分说“违反组合关系”。后来改成:
class Passenger {
private Luggage luggage;
public Passenger(double luggageWeight) {
this.luggage = new Luggage(luggageWeight);
}
public double getTotalWeight() { return 75.0 + luggage.getWeight(); }
}
class Luggage {
private double weight;
public Luggage(double weight) { this.weight = weight; }
public double getWeight() { return weight; }
}
这样Luggage彻底成为Passenger的一部分,外部无法单独修改行李重量,符合“行李随旅客创建”的真实场景。
4.2 WeightBalanceCalculator:纯工具类,依赖而非关联
这个类负责计算力矩、重心、%MAC。设计要求:不能有Flight的成员变量,必须把Flight对象作为参数传入。我开始不理解,觉得“每次都传参好麻烦”。后来意识到:如果Calculator里存了Flight,那它就和Flight绑死了,无法复用到其他机型。现在它是无状态的,任何航班都能用它计算。
公式实现上,我严格按照五步走:

  1. 旅客总重 = Σ(75 + 行李);力矩 = 总重 × 18.0
  2. 货舱总重 = 前舱重 + 后舱重;力矩 = 前舱重×12.0 + 后舱重×22.0
  3. 全机总重 = 40000 + 旅客总重 + 货舱总重;总力矩 = 40000×16.25 + 旅客力矩 + 货舱力矩
  4. 实际重心CG = 总力矩 / 总重
  5. %MAC = (CG - 15.0) / 5.0 × 100
    4.3 冒泡排序:手写稳定排序的折磨
    第三次作业明确禁止Collections.sort()和lambda,必须用循环实现冒泡排序。我一开始写的版本是这样的:
    for (int i = 0; i < n-1; i++)
    for (int j = 0; j < n-i-1; j++)
    if (list.get(j).getWeight() < list.get(j+1).getWeight())
    swap(j, j+1);
    看起来没问题,但测试时发现:重量相同的货物,输出顺序竟然和输入顺序不一样了!原来标准冒泡排序在weight1 < weight2时才交换,相等时不交换,这样能保证稳定性。而我的代码里没有考虑相等情况,实际上它会在相等时也保持不动,所以稳定性本该成立——那为什么顺序会变?排查后发现,是我在读取输入时把货物按照“先全部前舱、再全部后舱”的顺序加入列表,但排序后重排了,而题目要求的是所有货物一起排,相同重量时按输入顺序——输入顺序就是它们在整个输入流中的出现顺序。我的排序保持了稳定性,但因为输入顺序是按舱分段的,所以结果正确。真正差点出错的是:我把比较条件误写成<=,导致相等时也交换,破坏了稳定性。所以最终写法必须是<,不能有等号。
    4.4 输入校验:让程序变得“玻璃心”
    第三次要求:任何非法输入(负数、超出范围、类型错误)都要立刻输出提示并停止。我封装了一个InputValidator类,提供静态方法readInt和readDouble,内部用hasNextInt/hasNextDouble预判,再用范围检查。这样做之后,主函数变得异常干净,所有校验都集中在工具类里。
    例如读取前舱行数:
    int frontRows = InputValidator.readInt(sc, 0, Integer.MAX_VALUE, "前舱行数");
    如果用户输入负数或字符串,程序会输出“数值不能为负数!”或“输入必须在0到2147483647之间!”并退出。这大大增强了鲁棒性。
    4.5 浮点数边界判断的教训
    在判断%MAC是否在[25.0, 38.0]时,我直接写if (cgPercent >= 25.0 && cgPercent <= 38.0)。测试tc1(>38%)和tc10(<25%)时总是差一点。后来打印出cgPercent的值,发现由于浮点计算误差,实际值可能是24.9999999或38.0000001,导致边界判断错误。修正方法:引入一个很小的epsilon(1e-9),写成if (cgPercent >= 25.0 - 1e-9 && cgPercent <= 38.0 + 1e-9)。这样边界情况也能正确通过。

oop3

五、采坑汇总(数据说话)
下面这个表格是我三次作业中遇到的典型问题及解决过程:
image
最终所有测试点(10个)全部通过,内存稳定在19~21MB,时间不超过151ms,性能足够。

六、改进建议(如果让我再写一遍)

  1. 用配置文件管理机型参数:第三次作业里的空机重量、力臂、MAC等常量,如果以后换机型,就得改源码。可以改成从JSON读取,程序启动时加载,这样能适应多种飞机。
  2. 货舱数量动态化:目前硬编码前舱后舱,如果将来有中舱、侧舱,改动量不小。应该设计成读取配置时动态生成货舱列表,用Map存放,id不限于“1”“2”。
  3. 增加日志记录:现在全靠打印,出错了只能看最后一句。可以加入简单的日志(比如用java.util.logging),记录每一步装载成功/失败,方便回溯。
  4. 缓存总重量:每次调用getCurrentWeight()都要遍历货物列表,虽然n不大,但习惯不好。可以在addCargo时维护一个totalWeight变量,O(1)返回。
  5. 分离输出格式化逻辑:现在输出语句散落在各个类里,万一输出格式变了要改很多地方。可以专门写一个ReportFormatter类,负责所有格式化输出。

七、总结:从代码到工程思维的蜕变
这三次作业,表面上是写Java,实际上是让我体会了软件如何应对需求变化。第一次是单体,第二次拆出了组合/聚合,第三次加入了纯计算工具和严格校验,每一步都在体现“高内聚、低耦合”的设计思想。
我学会了:
• 不要在一开始就追求“完美设计”,但必须在每次迭代时重构。
• 浮点数比较一定要留误差余量。
• 输入校验不是“加分项”,而是基本要求。
• 冒泡排序虽然慢,但写一遍能让你真正理解稳定性。
接下来,我打算继续学习设计模式(尤其是工厂和策略模式),并尝试用JUnit为这些类写单元测试。另外,对于航空平衡,我还想了解更多关于横向平衡、动态重心变化的知识,把这次作业当成一个起点,而不是终点。

附录:三次作业最终类图
作业一:image

作业二:image

作业三:image

posted @ 2026-05-18 22:22  曼波爱吃凉皮  阅读(6)  评论(0)    收藏  举报