基于航空航班配载场景的面向对象程序设计迭代复盘总结
一、前言
这学期面向对象程序设计课程的三次迭代式作业,围绕“航空业航班配载”场景,让我从一个简单的小功能出发,逐步搭建起一个小型配载管理系统。整个过程慢慢迭代开发,每一次作业都在上一次的基础上叠加新的需求,让我们不断调整代码结构、优化设计思路,也让我对面向对象的思想有了从理论到实践的落地体会。
回顾这三次作业,我理解了很多在课本上看得懂,自己写不出,还经常容易混淆的知识点:
- 作业1:核心是理解单一职责原则。 不能把所有的逻辑都堆在一个类里,要把不同的功能拆分给不同的类,让每个类只负责自己的部分。在刚开始写代码时,我很习惯一个类包含多种方法,很多不同的业务逻辑放在一个类当中,这次让我打破了原有的面向过程思维。
- 作业2:开始深入面向对象的类间关系。 引入了组合与聚合的概念。我们要处理多货舱的管理,还有货舱内部位置网格,还要实现基于重量的排序装载算法。我学到了类和类之间不是随便调用的,它们的关系是有区别的。我们要理清它们的生命周期和依存关系,不会弄错类与类之间的关系。
- 作业3:引入了重心力矩计算。 对于处理数据校验有很高的要求。这让我深刻体会到,在实际的业务代码里,数据的严谨性直接决定了系统的生死,数据校验的重要性。
从题量和难度的变化来看,也能很明显地感受到迭代的处理问题。最直观的体现就是我的源码行数:第一次作业的时候,我只写了128行代码就能完成所有的功能,当时觉得也不过如此;到了第二次作业,代码到了199行,因为要处理多货舱、位置网格这些新的逻辑,这时候已经开始感觉有些类如果设计不好就会显得非常臃肿,逻辑很混乱,不好处理问题;第三次作业则直接到了276行,不仅要加旅客、行李的管理,还要处理力矩的计算公式,还有一大堆的数据校验。
为了更清晰地跟踪这三次迭代的代码质量变化,我使用了代码质量分析工具 SourceMonitor,还有 ProcessOn 来画类图,把整个过程的变化都量化了出来。这篇博客就是对这三次作业的完整复盘,讲讲我做了什么,踩了什么坑,还有学到了什么。
二、设计与分析
整个三次迭代的设计,其实就是一个不断拆分、不断优化的过程,从最开始的几个简单的类,慢慢拆成了职责清晰的10个类,每一次迭代都在调整结构,让代码更符合面向对象的设计原则。
1. 迭代一:基础配载模块的初步搭建
第一次作业是整个系统的起点,需求是处理货物,把它们按重量排序,然后输出装载的结果。为了符合单一职责,我的思路就是不能把所有东西都写在主类里,要把功能拆开来,每个人负责自己的部分。
我设计了四个类:
- Cargo:数据类,只负责存货物的名字和重量,没有任何业务逻辑。数据类就应该只存数据,不能让它管排序或者输出的事情。
- CargoSorter:排序工具类,我选择了选择排序,专门负责接收货物数据并返回排序后的结果。
- Loadmanifest:输出类,负责把最终的装载结果按照题目要求的格式输出。
- Flight:主协调类,负责把前面三个类串起来,处理用户的输入,然后调用排序、调用输出,不做具体的业务逻辑,只是负责协调各个模块。

2. 迭代二:多货舱与位置管理的结构升级
第二次作业,要求支持多货舱,每个货舱里面还有网格位置。飞机分为了多个不同的货舱,每个货舱内部划分了网格的位置。还要处理货物的装载,不能装错位置,也不能超载。
这次我新增了两个核心类:
- Position:位置类,负责管理货舱里的每个网格,里面存了这个位置的行、列。
- CargoCompartment:货舱类,它负责管理自己内部的所有位置,还有装进去的货物,处理装载的逻辑,比如能不能装下这个货物,装进去之后要更新位置的占用状态,还要更新当前的总重量。
这时我深入研究了组合和聚合的区别:
- CargoCompartment 和 Position 是组合关系:位置是货舱的一部分,货舱被创建时,位置就跟着创建了,生命周期相同,就像人和手。在代码里,我在构造函数里就把所有的格子全部初始化出来。
- CargoCompartment 和 Cargo 是聚合关系:Cargo 是可以脱离货舱独立存在的。它只是暂时被装进货舱里,哪怕货舱销毁了,货物本身在业务层面上依然是独立存在的,就像汽车和发动机。

3. 迭代三:航空器载重平衡计算的功能完善
第三次作业是三次里最复杂的,不仅要处理货物,还要加旅客、行李,还要计算整个飞机的总重、总力矩,还要算出 % MAC,还要做很严谨的数据校验。
新增类:
- Passenger:旅客类,存旅客的基本信息,还有他的行李,题目要求行李必须属于旅客,所以我把行李的创建放在了旅客的构造函数里。
- Luggage:行李类,存行李的重量,属于旅客。
- WeightBalanceCalculator:计算工具类,所有的力矩计算、总重计算、% MAC 的换算,都放在这个类里。这个类里的公式非常多,有空机的力矩,有旅客的力矩,有货物的力矩,每个的力臂都不一样,所有的计算逻辑都封装在这里。
这次迭代后,类的数量涨到了10个。职责非常清晰:旅客管旅客,计算管计算。当我发现最终数值对不上时,我不需要去翻解析输入的代码,只需要在计算工具类里检查公式就行,类间的职责分工明确,只管好自己的事。在校验逻辑这块,我的做法是,在主函数里对每一个输入的数据都做校验,比如判断是不是正数,是不是在范围内,是不是不超过货舱的剩余载重。所有进入计算环节的数据都是合法的,体现数据校验的重要性。

4. 代码质量:SourceMonitor 的分析
为了更直观地看到这三次迭代的代码质量变化,我用 SourceMonitor 工具对三次提交的源码做了量化分析,得到了下面的对比数据,这个数据也能很真实地反映我这三次迭代的变化:

这里我要解释一下,圈复杂度是用来衡量代码逻辑复杂度的指标,数值越低,说明代码的分支越少,越容易读懂,也越不容易出错,一般来说,圈复杂度低于 5 的话,就是非常好的代码,超过 10 的话就很难维护了。
从这个数据里能看到,虽然我加了很多功能,代码量涨了比较多,但是平均圈复杂度反而降低了,最大圈复杂度也始终控制在 5 以内,说明在迭代的过程中,没有把代码越写越乱,符合要求,而是通过不断的拆分类和方法,把原来的复杂逻辑拆成了小的、简单的逻辑,这也是单一职责原则带来的好处。
比如第一次作业的时候,Flight 类里有排序、输入、输出的逻辑,一个方法里有几十行代码,圈复杂度也比较高,到了第二次,我把货舱的逻辑拆出去了,到了第三次,又把计算的逻辑拆出去了,最后每个方法都只有几行或者十几行代码,逻辑非常清晰,所以复杂度就降下来了。
三、踩坑心得
这三次作业,我踩了不少坑,很多都是看起来很小的细节,但是排查起来花了很多时间,现在回头看,这些坑其实都是很好的学习机会,让我明白了很多编码里的细节问题。
1. 排序算法与精度问题的排查
第一次作业里,我卡的最久的就是排序的问题。题目要求用选择排序,但是我之前习惯用冒泡排序,所以一开始就直接写了冒泡排序,结果测试点怎么都过不了。当时是同学分享通过的方式才解决测试点问题。
还有一个就是精度问题,一开始我用 double 来存货物的重量,我测试了很多次数据都过不了测试点,后来我想起来,double 的精度问题,然后就换成了 BigDecimal,用 compareTo 方法来比较重量。虽然最终好像并不是精度的问题过不了测试点,但是我也学习巩固了下BigDecimal的语法。
2. 作业二:集合管理与状态同步的坑
第二次作业的坑主要在 List 的管理上,货舱里有两个 List,一个是positionList,存所有的位置,另一个是cargoList,存装进去的货物。这两个 List 是 private 修饰,然后提供了一个addCargo方法,在这个方法里,判断了当前的总重加上货物的重量有没有超过货舱的最大载重。把货物加到 cargoList 里。把对应的 position 的 isOccupied 改成 true。这样既简便又能很好的处理货物装载的判断问题。
3. 数据校验与引用共享的“漏洞”
第三次作业的坑最多,因为细节太多了,稍微漏一点就错了。
第一个坑就是 Passenger 和 Luggage 的关系,题目要求行李必须属于旅客,不能脱离旅客存在,我一开始是把 Luggage 作为参数传到 Passenger 的构造函数里,这样外面可以随便 new 一个 Luggage,然后传给 Passenger,甚至可以把同一个 Luggage 传给多个旅客,这就不对了。
后来我改成了,在 Passenger 的构造函数里,根据输入的行李重量,自己 new 一个 Luggage 对象,这样就保证了,每个旅客的行李都是自己的,不会被外面随便改,也不会出现一个行李属于多个旅客的情况。
第二个坑就是校验逻辑的问题,我把所有的校验都写在主函数里,每个输入都要写一个 if 判断,这个让我明白重复的代码太多了,应该把它封装成判断类,这样减少复制过程,也更简化代码量。
四、改进建议
1. 代码复用:抽象公共接口,减少冗余
现在的代码里,Cargo、Passenger、Luggage 这三个类,其实有很多共同点,它们都有重量,都有力臂,都要参与总重和总力矩的计算,但是现在我是分别处理它们的,遍历完货物列表,再遍历旅客列表,再遍历行李列表,代码有很多重复的地方。
我可以做一个抽象的父类,比如LoadItem,里面定义好 weight 和 arm 的属性,还有getTotalMoment的方法,然后让 Cargo、Passenger、Luggage 都继承这个类,这样计算总重和总力矩的时候,我只要把所有的 LoadItem 放到一个列表里,遍历一遍就可以了,不用分别遍历三个列表,这样代码就复用了,也更简洁。
而且以后如果要加新的装载项,比如邮件、燃油,只要继承 LoadItem 就行,不用改计算的代码,符合开闭原则。
2. 质量优化:降低圈复杂度,完善注释
从 SourceMonitor 的分析结果来看,还有两个可以优化的地方: 一个是圈复杂度,现在校验和输出的方法,圈复杂度还有点高,因为有很多 if 判断,我可以把这些方法拆成更小的方法,比如把输出拆成printCargoInfo、printPassengerInfo、printBalanceResult,每个方法只做一件事,这样圈复杂度就降下来了。 另一个就是注释,现在的注释率太低了,我也意识到要写好注释,不然过段时间自己都看不懂了,而且如果别人要看我的代码,没有注释也很难理解。
五、总结
1. 阶段学习收获
这三次作业下来,我学到的东西很多,比上课听老师讲理论要深刻的多。
然后,我明白了数据校验和编码规范的重要性,用户的输入不是永远正确的,你必须在最开始就把非法的输入挡住,不不然后的计算都是错的,而且排查起来很难。还有写代码时加上注释的重要性。
还有,我学会了用工具来分析代码质量,比如 SourceMonitor,很清晰看出代码的质量是可以量化的,可以通过复杂度、注释率这些指标来衡量,也可以将几次的代码对比比较得出自己的方向是否错误,是否走偏,这对我以后写代码帮助很大。
2. 后续研究方向
接下来,我还想深入学习一下设计模式,现在的代码虽然拆分了,但是还是比较简单,以后如果要处理更复杂的业务,比如不同的机型,不同的配载规则,我想研究一下怎么用策略模式、工厂模式来处理,让代码更灵活,不用每次加新的机型都改很多代码。
3. 课程优化建议
这三次迭代式的作业设计的很好,让我体会到了真实项目的开发过程,不过我也有一点小小的建议:
我觉得现在的作业,前后的衔接还可以再加强一点,现在每次作业的需求变动,我几乎要重新写大部分的代码,没有真正的在老代码上加新功能,比如第一次的代码,到第二次的时候,大部分都没用上,第三次又重新写,这样根本没有体会到重构老代码、给老代码加新功能的过程,感觉并不是真正的迭代。
我希望以后的作业,需求是逐步叠加的,第一次做了单货舱,第二次就在这个基础上加多货舱,第三次在这个基础上加力矩计算,这样我们就能真正的迭代,把老的代码重构,加新的功能,而不是每次都像做一个新的项目。
还有,希望老师能给我们一些工具的使用指导,比如 SourceMonitor、PowerDesigner 这些,我一开始用的时候,摸索了好久才会用,如果有一点指导的话,我们能更快上手,也能更早的用这些工具来分析自己的代码。
最后,这几次作业虽然写的很辛苦,经常要调试很久来通过测试点,但是也真的学到了很多实用的东西,这些东西比上课听的理论道理要深刻的多,更加理解了书本中的抽象的道理。

浙公网安备 33010602011771号