OOP前三次作业集总结
一、前言
本次面向对象程序设计课程三次作业集,以航空器配载与货运管理系统为场景展开。让学生通过迭代式的训练,题目将需求一步一步地增加和细化,使得场景功能更加完善,从作业一的一个货舱到作业二的多个货舱,同时给货舱增加行列网格的装载位置,能够实时判断货舱是否超载再装入货物,更加贴合真实场景。作业三就让代码更加使得系统的可行性和真实可靠性增加,明确了现实当中飞机上不仅仅存在货物,还存在旅客和行李,稍有差池,就可能导致飞机的不平衡,同时引进了重心和MAC的物理概念以及物理常量,使得计算更加科学。
知识点的考察也和题目需求息息相关,随之迭代升级。第一次作业首先提出了单一职责原则,以下所有的代码都要建立在这个原则之上。同时用面向对象编程不同于面向过程编程,封装作为面向对象的三大特性之一,也是学生写作业必须要实现的。同时考察使用Scanner输入的细节,稍有不注意,就会使得无法正确地接收到测试用例的输入,因此提示了用scanner.nextLine();该行代码专门接收上一行代码的回车符号。总的来说,第一次作业是指路人,让我第一次去体会面向对象编程的代码实现,需要理解并在代码中实现类间关系,同时因为题目要求不能使用list.sort()方法,但可以使用ArrayList或者LinkedList取代数组和链表,这就要求我掌握ArrayList的使用方式和选择排序的手动实现,另外严格的输出要求也是对学生的考察,初步理解和满足客户的需求,但是总体难度还是不大的。
第二次作业在第一次作业的基础上增加了类的数量,同时进一步地考察了组合和聚合的实现,细化了要求,增加了调度类和输入校验类,考察类的职责分离,通过类间关系来实现系统功能,难度较第一次明显加大;第三次作业继续迭代,引入旅客实体,新增基于物理力矩的计算功能,增强鲁棒性要求,类的数量进一步增加,同时考察了java中静态常量的使用,类间关系的复杂度增加也就导致思维难度的提高,是知识点的综合考察。
二、设计与分析
第一次作业设计与源码分析
(1)通过SourceMontor的生成报表内容来分析:

- 源码总行数142行、有效可执行语句87行:这说明源码的利用率比较高,能够实现题目的要求,严格按照题目的需求·。
- 分支语句占比10.3%:这说明代码绝大多数为线性业务逻辑,仅在排序、重量判断、超载判定处存在少量分支语句,这体现了代码的简洁和可读性,也正是因为分支语句if,else、循环的占比不大,所以没有单独提取成一个判断类来实现,符合了单一职责原则。
- 代码总调用次数23次:这里面包含了get和自定义的工具方法,类间与方法间调用交互的频率较少,类与类的解耦比较好。
- 注释率0.0%:这是因为作业并没有将注释作为得分点的原因,但是这样不利于后期迭代维护,是后续优化的重点,应该添加必要的注释,增加可读性和可维护性。
- 工具检测识别5个类:Flight、Cargo、CargoSorter、LoadManifest、Main类,也就是4个核心业务类+1个Main测试类,没有多余的类,是单一职责原则的体现。
- 类平均方法数4.40、单方法平均语句2.41条:代码将复杂的配载业务拆解为多个方法,每个方法只实现一个功能,比如判断是否超载、新增货物、统计重量等。
- 最大圈复杂度为4:复杂度处于行业1-10低复杂度区间,该复杂度是由于题目强制要求手写的选择排序算法,存在少量循环与判断逻辑,能够满足货物重量降序排序的要求。
- 最大嵌套深度5层:这超出行业3层以内的最佳实践标准,应该还是因为手写选择排序的原因,写了双层循环和条件判断,降低了代码可读性,但是这是题目强制要求的。
- 平均嵌套深度为1.76层,代码平均圈复杂度1.19:复杂度是衡量代码健壮性、可维护性的依据,这体现了整体几乎无复杂分支逻辑,绝大多数方法为线性业务逻辑,整体代码层级极浅、可读性高,简单易懂,除了手写选择排序的部分。
(2)通过PowerDesigner类图来进行分析:
![image]()
Flight 类中持有private LoadManifest manifest,构造函数自动初始化manifest = new LoadManifest(),外部通过getManifest()获取舱单,实现了每个航班必须有且仅有 1 个LoadManifest,舱单的生命周期与航班绑定,也就是1..1关联。
LoadManifest 类中持有ArrayList
LoadManifest 和CargoSorter是依赖关系,通过传入CargoSorter sorter参数进Sort方法,来完成排序,LoadManifest自身不直接实现排序逻辑,实现SRP。
第二次作业设计与源码分析
(1)通过SourceMontor的生成报表内容来分析:

- 源码总行数288行、有效可执行语句136行:相较于第一次作业142行总行数、87条有效语句,本次代码体量翻倍,有效语句大幅增加,代码利用率依旧较高。代码主要用于实现作业二的多个货舱,给货舱增加行列网格的装载位置,实时判断货舱是否超载再装入货物。
- 分支语句占比13.2%:相较于第一次作业提升较低,但整体占比仍处于较低水平,代码主体依旧以简洁线性业务逻辑为主。新增分支语句集中在Judgement类中,但没有大量复杂嵌套分支,这是因为题目要求遵守单一职责原则,于是我将Main类里面的大量判断抽离出来提取为Judgement类。
- 代码总调用次数29次:对比第一次作业略有增加,但是整体而言,调用频率依旧偏低,类间无高耦合、频繁嵌套调用问题,将解耦原则贯穿始终。
- 注释率0.0%:与第一次作业一致,本次源码仍未添加任何注释。因为代码量越来越大以及复杂程度随之提高,代码如果不加注释,之后的维护和理解就会更加困难,因此后续应该补充关键注释。
- 工具检测识别8个类:相较于第一次作业5个类的基础结构,本次拆分至8个业务类,包含Flight、Cargo、CargoCompartment、Position、LoadDispatcher、InputValidator、Judgement、Printer,以及Main类,没有多余的类,都是为了满足单一职责原则和本次作业的需求而设计的。
- 类平均方法数4.63、单方法平均语句2.08条:类平均方法数与第一次作业基本一致,各类职责分布均匀,单方法平均语句数量降低,方法更加细化。每个方法仅负责一项小功能,比如数据校验、判断、标准化输出等,落实单一职责原则。
- 最大圈复杂度为4:与第一次作业保持一致,始终处于行业低复杂度最优区间。本次最高复杂度逻辑仍来源于题目要求的手写选择排序算法,仅包含少量循环与条件判断逻辑,所有新增的校验、判定、输出逻辑均不复杂。
- 最大嵌套深度5层:与第一次作业一致,超出行业3层最佳实践标准。该缺陷依旧来源于手写选择排序模块,双层循环和条件判断造成嵌套层级过高,但是该问题为题目强制手写算法导致的。
- 平均嵌套深度1.70层、平均圈复杂度1.47:平均嵌套深度较第一次作业小幅降低,整体代码嵌套层级更浅;平均圈复杂度小幅上升,原因是本次新增多条件判断等少量分支逻辑。
(2)通过PowerDesigner类图来进行分析:
![image]()
CargoCompartment 和Position是组合关系,货舱在构造方法内部主动创建所有Position位置对象,外部不新建位置,货舱销毁,对应位置随之消失,位置无法独立存在。
CargoCompartment 和Cargo是聚合关系,Cargo货物由外部独立创建,可脱离货舱单独存在,货舱仅用集合存储货物引用,通过addCargo()完成装载。
Flight 和 CargoCompartment是关联关系,航班类持有货舱集合作为成员属性,提供按ID查找货舱方法;LoadDispatcher 和Cargo是依赖关系,调度类在排序、查找方法中以形参接收货物列表。
第三次作业设计与源码分析
(1)通过SourceMontor的生成报表内容来分析:

- 方法调用数:由29次增至74次:本次开发为适配复杂业务,将大量独立、重复的业务逻辑封装为专属方法,模块分工清晰,但是无过度拆分问题。
- 单方法平均语句数:由2.08条增至4.05条,近乎翻倍:目前4.05条的平均值,远优于行业单方法不超10条语句的规范。
- 分支语句占比:由13.2%升至16.1%:分支占比提升是业务复杂度升级的正常表现,仍然保持了代码可读性与可维护性。
(2)通过PowerDesigner类图来进行分析:
![image]()
Passenger与 Luggage 是组合关系,行李对象在Passenger构造器内部new出来,不对外暴露修改;Luggage作为Passenger的组成部分;WeightBalanceCalculator与 Flight是依赖关系,将Flight对象作为generateLoadSheet方法的参数传入;InputValidator与主流程是依赖关系;Flight和Passenger是关联关系。
三、踩坑心得
第一次作业踩坑:
- Scanner缓冲区残留问题,初始编写的代码存在严重的Scanner缓冲区处理逻辑错误,遇到不同的测试用例会出现输入错误,后来通过添加if(i!=n-1){scanner.nextLine();},仅在非最后一次循环时吸收回车换行符,最后一次循环不执行缓冲区清空操作来解决了这个问题。
![image]()
第二次作业踩坑:
1.初期代码混淆两种关系,Flight与CargoCompartment为聚合关系,而CargoCompartment与Position为组合关系,未在货舱构造方法中初始化位置集合,后来在CargoCompartment带参构造方法中,直接初始化Position集合,通过双层循环自动生成对应行列的位置网格。
2. 超载判断逻辑杂乱,将单货舱超载判定、航班最大起飞重量超载、航班最大业载超载的判断逻辑,全部混写在主类中,后来独立拆分Judgement工具类,实现判定逻辑的解耦,封装三个独立静态方法:CcOverLoad()负责单货舱超载判定、FlightOverMaxWeight()负责航班最大起飞重量判定、FlightOverMaxLoad()负责航班最大业载判定。
3. Main主类职责高度混杂,初期编写代码违背单一职责原则,将输入、货物排序算法、货舱装载业务、超载判定、输出打印等全部写在Main类中。后来新增Judgement判定工具类;新增Printer打印工具类,统一标准化输出格式,使得Main类简洁单一了许多,相对符合SRP。
第三次作业踩坑:
-
系统常量定义不规范,违背封装原则。初期直接使用 public static final 修饰航空核心常量。所有系统预设常量完全对外暴露,无任何权限隐藏,外部任意类都可以直接访问静态常量值。后来将所有航空业务常量统一修改为 private static final 私有化修饰,禁止外部类直接访问数据。同时在WeightBalanceCalculator工具类中提供对应静态getter方法,对外仅提供查询入口。
-
初期定义getLuggage()方法向外暴露行李私有对象,外部可直接获取旅客内部行李,破坏封装;Luggage类提供公开有参构造方法,允许外部随意实例化行李对象,违背组合。后来重构Passenger构造方法,行李对象完全由旅客内部主动new创建,外部仅传入行李重量参数,实现旅客与行李的组合关系。
![image]()
-
初期代码存在流程顺序错误,优先打印货舱装载状态,再执行货物添加逻辑。程序运行时,货舱重量统计输出为0.0kg,但下方却正常列出已装载的货物明细,后来调整顺序解决问题;对题目的理解不到位,认为警告但是继续添加货物,后来在addCargo方法中,在货舱超载判定分支中,调用System.exit(0)强制终止程序。
![image]()
四、改进建议
第一次作业改进:
1.LoadManifest的getCargoList()方法直接返回内部ArrayList原始引用,外部可随意增删集合元素,严重破坏封装性。优化后返回集合新副本,仅对外提供只读访问能力。
2.规范命名,将addCargotoList修正为addCargoToList、大写开头的Sort改为sort,将Cargolist变量名规范为cargoList等。
3.删除不必要的Setter方法,杜绝数据篡改。
4.将原Main方法代码拆分,抽离printManifest()等方法。
第二次作业改进:
将Judgement工具类中FlightOverMaxWeight()、FlightOverMaxLoad()方法名优化统一添加is前缀,重构为isOverMaxWeight()、isOverMaxLoad(),见名知意,提升代码可读性。
第三次作业改进:
1.残留大量未调用冗余代码,包括LoadDispatcher.sortCargos()方法、整个Judgement判定工具类、Position.getPosName()方法等,统一清理删除无用代码。
2.原代码虽然独立封装Printer打印类,但在WeightBalanceCalculator.generateLoadSheet()仍存在输出逻辑,优化后计算工具类仅负责计算,所有控制台输出逻辑统一通过Printer类实现,严格遵循单一职责原则。
五、总结
心得收获:
1.写代码前必须正确理解需求,这样才能贴合实际的功能实现。
2.区分了聚合与组合的核心差异,能够正确实现类间关系,能够根据UML类图来架构代码。
3.封装性的要求实现,核心数据必须私有化隐藏,通过统一对外接口访问,杜绝权限泄露,同时掌握了系统常量的标准化定义规范。
4. 理解了Scanner混合输入的底层运行机制,输入流容错必须全覆盖,任何特殊输入都要能够正确处理。
5.深刻理解了必须遵循单一职责原则,比如遇到多条件的业务判断,将通用判定逻辑抽离为工具类,简化主流程代码。
进一步学习和研究:
1.学习给关键方法添加注释,不盲目写getter,setter方法,命名要规范,做到见名知义。
2.进一步学习java软件设计原则,掌握继承、多态、接口、抽象等实现。
3.研究设计模式,实现深层解耦。
4.优化算法能力,降低代码复杂度。
5.多练习题目,提高理解客户需求的能力,严格匹配功能实现。






浙公网安备 33010602011771号