第二次大作业航空货运管理系统
航空货运管理系统总结性blog
一、前言
本阶段两次题目集主要围绕面向对象编程和对航空订单运费进行计算以及航空订单信息进行处理输出。
感受:
两次的航空货运管理系统作业为迭代作业,核心算法较少,但代码量较大,第一次主要为类设计,第二次则体现继承与多态,在第一次的基础上增加不同运输货品种类,不同种类计费费率不一致,还增加支付宝、现金等支付方式,尽显程序设计的开闭原则,对扩展开放对修改封闭,在之后对代码功能进行完善的过程中,就可以保证代码安全性复用性。
知识点:
-
单一职责原则(SRP, Single Responsibility Principle)
含义:一个类应该只有一个引起它变化的原因(即只负责一个功能)。
核心:避免“万能类”,提高代码的可维护性和可读性。 -
里氏代换原则(LSP, Liskov Substitution Principle)
含义:子类必须能够完全替代父类,且不影响程序的正确性。
核心:继承关系应确保父类行为不被破坏,子类可以扩展但不能修改父类的原有契约。 -
开闭原则(OCP, Open-Closed Principle)
含义:软件实体(类、模块、函数)应对扩展开放,对修改封闭。
核心:通过抽象(接口、抽象类)和多态(继承、实现)来扩展功能,而非直接修改已有代码。 -
合成复用原则(CRP, Composite Reuse Principle)
含义:尽量使用组合(has-a)而非继承(is-a)来复用代码。
核心:通过对象组合(依赖注入、成员变量)实现功能复用,降低类之间的耦合度。 -
依赖倒置原则(DIP, Dependency Inversion Principle)
含义:高层模块不应依赖低层模块,二者都应依赖抽象(接口/抽象类)。抽象不应依赖细节,细节应依赖抽象。
核心:通过依赖注入(如构造函数、Setter、接口)解耦模块,提高灵活性。
这五个原则共同目标是:
1、高内聚低耦合(模块职责单一,依赖合理)
2、易扩展(新增功能不修改旧代码)
3、强健壮性(子类可安全替换父类)
4、复用性(优先组合,减少继承滥用)
它们是面向对象设计的核心准则,用于构建灵活、可维护的系统。
难度:
两次题集整体难度不大,主要围绕类设计,迭代后主要围绕将类拓展,增加功能。
二、设计与分析
针对第一次的航空货运管理系统进行类的分析
- Main 类
职责:程序的入口,协调各个模块的执行流程
协作关系:
创建 InputHandler 读取输入
创建 OrderProcessor 处理订单
创建 OutputHandler 输出结果
评价:纯粹的协调者,职责单一 - InputHandler 类
职责:处理所有输入数据的读取和解析
主要功能:
读取客户信息
读取货物信息(多件)
读取航班信息
读取订单信息
协作关系:
创建 InputData 对象
创建多个 Goods 对象
评价:输入处理集中在一个类中,职责明确 - OrderProcessor 类
职责:处理核心业务逻辑
主要功能:
计算每件货物的计费重量
计算运费
检查航班载重限制
计算总支付金额
协作关系:
使用 RateCalculator 计算费率
创建 OrderResult 对象
创建多个 GoodsDetail 对象
评价:业务逻辑处理中心,职责清晰 - RateCalculator 类
职责:根据重量计算费率
主要功能:
实现分段费率计算逻辑
协作关系:
被 OrderProcessor 调用
评价:单一的计算功能,但实现方式较简单 - OutputHandler 类
职责:格式化并输出结果
主要功能:
输出订单概要信息
输出货物明细
协作关系:
使用 OrderResult 中的数据
评价:纯粹的展示逻辑,职责单一 - 数据类(InputData/OrderResult/Goods/GoodsDetail)
共同职责:作为数据传输对象(DTO)
特点:
仅包含属性和getter方法
无业务逻辑
评价:纯粹的数据载体,职责单一
类图:

根据使用SourceMontor对我的第一次航空货运管理系统程序进行分析,结果如下

文件信息
文件名:Main.java
总行数 (Lines):406
有效语句数 (Statements):83
文件实际执行的逻辑代码量较多。
分支语句百分比 (Percent Branch Statements):9.6%
分支语句比例适中,说明文件逻辑并非过于复杂。
函数调用语句数 (Method Call Statements):58
函数调用的数量占有效语句的其中一部分,暗示该程序具有较多的功能调用。
注释比例 (Percent Lines with Comments):3.4%
注释行占比非常低,仅 3.4%。需适当增加注释,提高代码的可读性和可维护性。
类与方法信息
类数 (Classes and Interfaces):5
方法数/类 (Methods per Class):3方法数/类 (Methods per Class):1
每个类包含 1个方法,属于常见的类设计结构。
方法平均语句数 (Avg. Statements per Method):13.8
每个方法平均包含 13.8 条语句,方法粒度较细,有助于代码的可读性和可测试性。
复杂度与深度指标
最复杂方法 (Most Complex Method):
RateCalculator.calculateRate()
最大复杂度 (Maximum Complexity):5
表示控制流复杂度较高(常见值为 1-10),说明代码逻辑不算复杂。
最大嵌套深度 (Maximum Block Depth):3
表示嵌套层次较少(如 if、while 等嵌套)。这是良好设计的体现。
平均块深度 (Average Block Depth):2
平均嵌套深度为 2,进一步确认了代码的逻辑相对直观。
Kiviat GraphKiviat 图
Avg Complexity:平均复杂度较高,逻辑较为复杂。
% Comments:注释比例低于理想值,需要优化注释。
Max Depth 和 Max Complexity:值中等偏高表明代码没有过深的嵌套,也没有复杂的分支逻辑。
Block Histogram块直方图
块结构语句分布显示:
大多数块的语句数为 2-3,且没有超过 4 语句的块。表明方法实现短小精悍,符合最佳实践。
第二次航空货运管理系统程序是对第一次的迭代,主要对用户、支付方式、货物类进行拓展,并遵循开闭原则,方便对类进行进一步的拓展
针对第二次的航空货运管理系统进行类的分析
- Main类
核心职责:程序入口,协调各模块执行流程
协作关系:
创建InputHandler读取输入
创建OrderProcessor处理订单
创建OutputHandler输出结果
变化:相比前一版本无实质变化 - InputHandler类
新增功能:
读取客户类型(Individual/Corporate)
读取货物类型(Normal/Dangerous/Expedite)
读取支付方式(Cash/Wechat/Alipay)
协作关系:
仍返回InputData对象,但包含更多字段
评价:输入处理依然集中,但需处理更多数据类型 - OrderProcessor类
业务逻辑增强:
新增货物类型费率计算(调用RateCalculator)
新增客户折扣计算(调用CustomerDiscount)
记录原始运费和折扣后运费
协作关系:
新增与RateCalculator、CustomerDiscount的交互
生成的GoodsDetail现在包含原始运费(ordinalFreight)
评价:业务处理更复杂,但仍保持单一职责 - 新增RateCalculator类
专属职责:根据货物类型和重量计算费率
核心算法:
三段式重量分级(<20kg, <50kg, <100kg, ≥100kg)
支持Normal/Dangerous/Expedite三种货物类型
评价:将费率计算从OrderProcessor解耦,职责更清晰 - 新增CustomerDiscount类
专属职责:根据客户类型计算折扣率
实现逻辑:
Individual客户:9折(10%折扣)
Corporate客户:8折(20%折扣)
评价:折扣策略独立封装,便于未来扩展 - OutputHandler类
输出增强:
显示支付方式(现金/微信/支付宝)
货物明细显示原始运费
变化点:
支付方式本地化显示(Cash→"现金")
输出格式微调
评价:展示逻辑依然独立,适应新需求 - 数据类升级
InputData新增字段:
customerType(客户类型)
goodsType(货物类型)
paymentMethod(支付方式)
OrderResult新增功能:
paymentMethod字段及本地化转换逻辑
输出时显示支付方式
GoodsDetail新增字段:
ordinalFreight(折扣前原始运费)
类图:

根据使用SourceMontor对我的第二次航空货运管理系统程序进行分析,结果如下

文件信息
文件名:Main.java
总行数 (Lines):361
有效语句数 (Statements):173
文件实际执行的逻辑代码量较多。
分支语句百分比 (Percent Branch Statements):16.8%
分支语句比例适中,说明文件逻辑并非过于复杂。
函数调用语句数 (Method Call Statements):65
函数调用的数量占有效语句的其中一部分,暗示该程序具有较多的功能调用。
注释比例 (Percent Lines with Comments):1.7%
注释行占比非常低,仅 1.7%。需适当增加注释,提高代码的可读性和可维护性。
类与方法信息
类数 (Classes and Interfaces):8
方法数/类 (Methods per Class):3方法数/类 (Methods per Class):2.38
每个类包含 2.38个方法,属于常见的类设计结构。
方法平均语句数 (Avg. Statements per Method):6.89
每个方法平均包含 6.89 条语句,方法粒度较细,有助于代码的可读性和可测试性。
复杂度与深度指标
最复杂方法 (Most Complex Method):
RateCalculator.calculateRate()
最大复杂度 (Maximum Complexity):14
表示控制流复杂度较高(常见值为 1-10),说明代码逻辑非常复杂。
最大嵌套深度 (Maximum Block Depth):4
表示嵌套层次较少(如 if、while 等嵌套)。这是良好设计的体现。
平均块深度 (Average Block Depth):2.2
平均嵌套深度为 2.2,进一步确认了代码的逻辑相对直观。
Kiviat GraphKiviat 图
Avg Complexity:平均复杂度较高,逻辑较为复杂。
% Comments:注释比例低于理想值,需要优化注释。
Max Depth 和 Max Complexity:值中等偏高表明代码没有过深的嵌套,也没有复杂的分支逻辑。
Block Histogram块直方图
块结构语句分布显示:
大多数块的语句数为 2-4,且没有超过 5 语句的块。表明方法实现短小精悍,符合最佳实践。
三、踩坑心得
1. 数据管理混乱问题
问题代码片段:
// 旧版Main类中直接使用分散的变量
String customerName = scanner.nextLine();
int goodsWeight = Integer.parseInt(scanner.nextLine());
// ...其他10多个分散的变量
问题分析:
变量散落在各处,方法参数越来越长
添加新字段(如客户类型)时需要修改多处
数据传递容易出错,比如把goodsWeight误传为volumeWeight
踩坑经历:
在添加"货物类型"功能时,我漏改了processOrder方法的两个参数传递,导致计算一直出错,花了2小时才找到这个bug。
2. 上帝类问题
问题代码片段:
// 旧版OrderProcessor中混杂的功能
public void processOrder() {
// 读取输入
// 计算重量
// 计算运费
// 验证航班
// 生成输出
}
问题分析:
一个方法做了太多事情(违反单一职责)
修改运费计算可能意外影响输出逻辑
代码超过200行,难以维护
实际教训:
当需要修改输出格式时,我不小心改动了重量计算的代码,导致运费计算出错,却直到演示时才发现。
3. 硬编码问题
问题代码片段:
// 硬编码的费率计算
if(weight < 20) {
return 35; // 普通货物费率
}
// 其他重量段...
问题分析:
费率数字直接写在代码中
添加危险品费率时被迫重写整个方法
无法灵活应对费率调整
踩坑经历:
当作业要求增加危险品费率时,我不得不把整个if-else结构重写为switch-case,期间因为漏写一个break导致计算结果错误。
四、改进建议
1. 数据封装优化
优化后代码:
// 使用专门的数据类
public class OrderRequest {
private Customer customer;
private List
private Flight flight;
// 清晰的getter/setter
}
// 使用示例
OrderRequest request = inputHandler.readInput();
优化点:
所有输入数据封装在一个对象中
添加新字段只需修改一个类
数据传递更安全(通过getter访问)
2. 类职责拆分
优化后结构:
// 拆分后的类
class WeightCalculator {
public double calculate(Goods goods) {...}
}
class FreightCalculator {
public double calculate(Goods goods, RateType type) {...}
}
class FlightValidator {
public void validate(double totalWeight, Flight flight) {...}
}
优化点:
每个类只负责一个明确的功能
修改重量计算不会影响运费计算
更容易编写单元测试
3. 可配置化设计
优化后代码:
// 费率配置类
public class RateConfig {
private Map<GoodsType, Map<WeightRange, Double>> rates;
public double getRate(GoodsType type, double weight) {
// 从配置map中查找
}
}
// 使用示例
double rate = rateConfig.getRate(goods.getType(), weight);
优化点:
费率规则可配置,不写死在代码中
添加新货物类型只需更新配置
支持从文件/数据库读取配置
4. 测试友好设计
可测试的代码示例:
// 独立的运费计算器
public class FreightCalculator {
private final RateStrategy rateStrategy;
// 依赖注入
public FreightCalculator(RateStrategy rateStrategy) {
this.rateStrategy = rateStrategy;
}
public double calculate(Goods goods) {
double rate = rateStrategy.getRate(goods);
return goods.getWeight() * rate;
}
}
// 测试用例
@Test
void testCalculate() {
// 可以使用模拟策略
RateStrategy mockStrategy = Mockito.mock(RateStrategy.class);
when(mockStrategy.getRate(any())).thenReturn(30.0);
FreightCalculator calculator = new FreightCalculator(mockStrategy);
Goods testGoods = new Goods(50);
assertEquals(1500, calculator.calculate(testGoods));
}
优化点:
通过依赖注入使代码可测试
可以使用Mock对象隔离测试
每个测试用例都很专注
关键改进总结表
| 问题类型 | 旧代码问题 | 改进方案 | 受益点 |
|---|---|---|---|
| 数据管理 | 分散变量 | 封装为数据类 | 修改更安全,传递更方便 |
| 类职责 | 上帝类 | 拆分为多个专业类 | 修改影响范围小 |
| 硬编码 | 数字直接写死 | 配置化设计 | 更容易适应变化 |
| 可测试性 | 难以测试 | 依赖注入+接口 | 可以写单元测试 |
五、总结
1. 学到了什么?
1.1 面向对象编程核心原则
单一职责原则:每个类/方法应该只做一件事(如InputHandler只负责输入,OrderProcessor只负责处理)
开闭原则:通过扩展而非修改来增加新功能(如使用RateStrategy接口支持新货物类型)
依赖倒置:高层模块不依赖低层细节(OrderProcessor依赖抽象的RateCalculator接口)
1.2 代码组织技巧
分层架构:明确划分输入→处理→输出层次(输入层、业务层、展示层分离)
数据封装:使用专门的数据类(如OrderRequest、GoodsInfo)替代分散的变量
配置化思维:将易变规则(费率、折扣)从代码中抽离
1.3 开发实践
增量开发:先实现核心功能(基础运费计算),再迭代增强(添加货物类型/折扣)
防御性编程:添加输入验证(如航班载重检查)和异常处理
测试驱动:为关键计算逻辑(如计费重量计算)编写单元测试
2. 哪些地方需要加强?
2.1 编码能力
设计模式应用:对策略模式、工厂模式等经典模式的实际运用还不够熟练
接口设计:定义抽象接口时对扩展性的考虑不足(如最初未考虑多种支付方式)
代码复用:存在重复计算逻辑(如多次计算体积重量)
2.2 工程实践
单元测试覆盖:未对边缘情况(如超重货物)进行充分测试
文档注释:方法级别的文档注释不完善
版本控制:提交记录描述不够规范(如"fix bug"这类模糊描述)
2.3 架构思维
依赖管理:类之间的耦合度还可以进一步降低
配置管理:硬编码的费率/折扣应该改为外部配置
扩展设计:对可能的变化点(如新增运输规则)预见性不足
3. 改进建议
3.1 对课程的建议
增加设计模式实战:在基础OOP之后,用1-2个课时演示策略模式/工厂模式的实际应用
提供代码模板:给出分层架构的基础模板(如标准的input-process-output结构)
加强测试教学:演示如何为业务逻辑编写单元测试(特别是边界条件测试)
作业梯度设计:将大作业拆分为多个checkpoint(如先实现基础功能,再迭代增强)
3.2 对作业的建议
明确设计约束:提前说明需要应用哪些设计原则(如第二次作业才明确要求依赖倒置)
提供测试用例:给出部分边界测试用例(如超重货物、异常输入)
增加代码审查环节:同学间互相review代码,学习优秀实现
优化评分标准:对配置化、可测试性等工程实践给予适当加分
3.3 自我改进计划
重构练习:用学到的原则重写第一次作业的代码
模式学习:重点掌握策略模式和工厂模式的应用场景
测试实践:为现有代码补充单元测试(特别是业务计算逻辑)
代码阅读:分析GitHub上类似项目的架构设计
这两次作业让我深刻体会到:好的代码不是写出来的,而是改出来的。从第一次作业的"能跑就行",到第二次开始考虑扩展性和维护性,这种思维转变比学会任何具体技术都更有价值。建议后续可以继续通过这种"迭代式"的作业设计,让我们在实践中逐步提升软件设计能力。
浙公网安备 33010602011771号