PTA题目集8-9-----航空货运管理系统的迭代分析
前言
这次实验像一场 “面向对象” 的实战演练,让我真正体会到类的封装、继承、多态不是停在课本的概念,而是能把现实问题拆分成清晰的代码结构。比如设计航空货运系统时,把客户、货物、航班这些 “角色” 抽象成类,每个类只管自己的 “事儿”(单一职责原则),像货物类只算运费,航班类只负责载重校验,代码一下就不乱了。
最有意思的是多态的应用,不同货物(比如发电机和信号发生器)用同一个 “计算运费” 的方法名,但各自算的逻辑不一样,这种 “统一接口、不同实现” 的感觉很巧妙。不过刚开始接触设计原则时挺懵的,比如怎么才算 “里氏代换”、怎么用 “开闭原则” 改代码,只能边写边琢磨,慢慢发现合理的类设计能让代码像搭积木一样,新增功能时不用大改原有逻辑,这大概就是面向对象的魅力吧。
虽然写代码时还会纠结属性该放哪个类、方法怎么调,但每调整一次类结构,就觉得离 “像样的设计” 更近一步,这种从模糊到清晰的过程,比单纯写对代码更有收获。
第8次题目集
航空快递以速度快、安全性高成为急件或贵重物品的首选。本题目要求对航空货运管理系统进行类设计,具体说明参看说明文件。
OO第九周作业题目说明.pdf
输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。
客户编号
客户姓名
客户电话
客户地址
运送货物数量
货物编号
货物名称
货物宽度
货物长度
货物高度
货物重量
航班号
航班起飞机场
航班降落机场
航班日期(格式为YYYY-MM-DD)
航班最大载重量
订单编号
订单日期(格式为YYYY-MM-DD)
发件人地址
发件人姓名
发件人电话
收件人地址
收件人姓名
收件人电话
输出格式:
如果订单中货物重量超过航班剩余载重量,程序输出The flight with flight number:航班号 has exceeded its load capacity and cannot carry the order. ,程序终止运行。
如果航班载重量可以承接该订单,输出如下:
客户:姓名(电话)订单信息如下:
航班号:
订单号:
订单日期:
发件人姓名:
发件人电话:
发件人地址:
收件人姓名:
收件人电话:
收件人地址:
订单总重量(kg):
微信支付金额:
货物明细如下:
明细编号 货物名称 计费重量 计费费率 应交运费
1 ...
2 ...
注:输出中实型数均保留1位小数。
输入样例:
在这里给出一组输入。例如:
10001
郭靖
13807911234
南昌航空大学
2
101
发电机
80
60
40
80
102
信号发生器
55
70
60
45
MU1234
昌北国际机场
大兴国际机场
2025-04-22
1000
900001
2025-04-22
南昌大学
洪七公
18907912325
北京大学
黄药师
13607912546
输出样例:
在这里给出相应的输出。例如:
客户:郭靖(13807911234)订单信息如下:
航班号:MU1234
订单号:900001
订单日期:2025-04-22
发件人姓名:洪七公
发件人电话:18907912325
发件人地址:南昌大学
收件人姓名:黄药师
收件人电话:13607912546
收件人地址:北京大学
订单总重量(kg):125.0
微信支付金额:3350.0
货物明细如下:
明细编号 货物名称 计费重量 计费费率 应交运费
1 发电机 80.0 25.0 2000.0
2 信号发生器 45.0 30.0 1350.0
题目分析
一、题目是干啥的?
这题就是让你模拟一个航空货运系统,处理订单流程。简单说就是:
先输入一堆信息:客户信息、货物信息(可能有多个)、航班信息、订单信息。
算运费:每个货物的运费由重量和体积决定,取大的那个算钱。
检查能不能运:如果货物总重量超过航班载重,就拒绝订单;否则就输出订单详情。
二、关键难点在哪?
运费计算规则:
体积重量 = (长 × 宽 × 高) ÷ 6000
计费重量 = 毛重和体积重量里的最大值
费率根据重量分档(比如 < 20kg 是 35 元 /kg,20-50kg 是 30 元 /kg)
输入顺序巨坑:
必须按客户→货物→航班→订单的顺序输入,而且货物可能有多个(由客户信息里的 “货物数量” 决定)。
用 Scanner 读输入时,换行符处理不好就会出错(比如读完一个数字,下一个字符串会直接读换行符)。
类怎么设计?
每个东西都要抽象成类(客户、货物、航班、订单),每个类管自己的事。
比如货物类要能算自己的运费,航班类要能检查载重,订单类要能汇总所有信息。
三、解题步骤
先理清类关系:
客户类:存姓名、电话、地址。
货物类:存重量、尺寸,算体积重量和最终运费。
航班类:存航班号、载重,检查是否超重。
订单类:把前面三个串起来,算总运费,输出结果。
实现核心功能:
算重量:写个工具类,专门比较毛重和体积重量,取最大值。
算费率:在货物类里根据重量分档算钱(比如 < 20kg 是 35 元 /kg)。
处理输入输出:
输入:按顺序读,注意换行符问题(读完数字后加个nextLine()吃掉换行符)。
输出:按题目给的格式打印,注意小数保留 1 位(用printf("%.1f"))。
调试技巧:
每读完一部分输入,就打印出来检查(比如读完货物信息,打印总重量)。
用样例输入一步步验证,看看每一步算出来的结果对不对。
我的类图
类的分析
People类
作为抽象类,将姓名、电话、地址等公共属性提取出来,为Customer、Sender、Recipient等子类提供基础属性,这种设计减少了代码冗余。
继承自People类,增加了货物列表相关操作方法(add和display) 。通过继承复用了People类的属性,拓展了客户相关功能。然而,客户类和航班类都持有货物列表,存在一定的数据冗余与职责划分不清晰问题。
Good类
涵盖了货物的基本属性(编号、名字、尺寸、重量)及相关操作方法。其中,displaygrand作为静态方法,方便在主类中直接调用以输出货物清单背景信息,设计较为巧妙。price、comparaTo(疑似拼写错误,应为compareTo )、Rate等方法分别负责运费计算、重量比较及费率获取,功能较为全面。
Plane类
对航班相关信息(航班号、时间、载重量、货物列表等)及操作(添加货物、载重检查、重量计算、运费计算、信息输出)进行封装。check方法用于判断货物总重量是否超出飞机最大载重量,是保障航班运输安全的关键逻辑。
Order类
定义了订单号、订单日期、支付方式等属性及相关输出方法。但displaypayment方法仅输出支付方式与金额,对于订单的整体信息展示不够全面,可考虑将订单号、日期等信息一并纳入输出内容,以提供更完整的订单信息展示。
Sender类与Recipient类
均继承自People类,实现了display方法用于输出信息。但二者与Customer类在功能上存在一定重叠,需进一步梳理职责,明确彼此间的差异与分工,避免功能冗余。
代码复杂度分析
整体概况
代码规模:
代码行数(Lines)有 231 行,不算特别长,但也不算短。语句数(Statements)是 141 条 ,说明不是全是代码逻辑,可能有一些空行、声明等。
类和接口数量(Classes and Interfaces)为 7 个 ,平均每个类的方法数(Methods per Class )是 3.00 个,总共大概有 21 个方法左右。这意味着代码有一定的模块化,把不同功能分散到多个类里了。
注释情况:
带注释的代码行百分比(Percent Lines with Comments )只有 3.9% ,说明代码里注释特别少。以后再看这段代码,或者别人接手,可能得花不少时间去理解代码逻辑。
代码复杂度相关
方法调用情况:
方法调用语句数(Method Call Statements )有 54 条,说明代码里各个方法之间的交互比较频繁,不同类的功能相互协作来实现整个航空货运系统的功能。
分支语句占比:
分支语句百分比(Percent Branch Statements )是 6.4% ,分支语句一般就是像if - else、switch这种,说明代码里做条件判断的地方不算多,逻辑相对比较直接,没有特别多复杂的条件判断逻辑。
最复杂方法:
最复杂方法的行号(Line Number of Most Complex Method )是 80 ,方法名(Name of Most Complex Method )是Rate.calculateRate() 。从名字看,这方法应该是在计算运费的费率。它这么复杂可能是因为里面要根据不同重量区间设置不同费率,得写一堆if - else条件判断。
可视化图表解读
Kiviat Graph(雷达图):
从图上看,各项指标分布不太均匀。比如 “Max Complexity(最大复杂度)”“Max Depth(最大深度)” 这些可能超出了理想范围,说明代码里有个别地方逻辑比较复杂。而像 “% Comments(注释百分比)” 这块就特别低,再次印证代码注释太少的问题。
Block Histogram(柱状图 - 语句数与深度):
可以看到在深度为 2 的地方语句数最多,说明大部分代码的逻辑层次不是特别深,但也有一些深度更高的部分,可能就是那些比较复杂的方法或者逻辑嵌套的地方。
改进建议
增加注释:
这么少的注释,后期维护太困难,把每个类、每个方法的功能,以及关键代码段的作用都加上注释。
优化复杂方法:
Rate.calculateRate()方法这么复杂,看看能不能把里面的逻辑拆分开,比如把不同重量区间的判断逻辑写成单独的小方法,让代码更好理解和维护。
检查代码结构:虽然有一定的模块化,但再审视下类与类之间、方法与方法之间的关系,看看能不能进一步优化,让代码的逻辑更清晰,调用关系更简洁。
小结
本次作业我的代码设计采用面向对象思想,类职责有划分但存问题。如People抽象性待加强,部分类间关系欠清晰,方法功能需完善。
第九次题目集
航空快递以速度快、安全性高成为急件或贵重物品的首选。本题目要求对航空货运管理系统进行类设计,具体说明参看说明文件。
OO第十二周作业题目说明.pdf
输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。
客户类型[可输入项:Individual/Corporate]
客户编号
客户姓名
客户电话
客户地址
货物类型[可输入项:Normal/Expedite/Dangerous]
运送货物数量
货物编号
货物名称
货物宽度
货物长度
货物高度
货物重量
航班号
航班起飞机场
航班降落机场
航班日期(格式为YYYY-MM-DD)
航班最大载重量
订单编号
订单日期(格式为YYYY-MM-DD)
发件人地址
发件人姓名
发件人电话
收件人地址
收件人姓名
收件人电话
支付方式[可输入项:Wechat/ALiPay/Cash]
输出格式:
如果订单中货物重量超过航班剩余载重量,程序输出The flight with flight number:航班号 has exceeded its load capacity and cannot carry the order. ,程序终止运行。
如果航班载重量可以承接该订单,输出如下:
客户:姓名(电话)订单信息如下:
航班号:
订单号:
订单日期:
发件人姓名:
发件人电话:
发件人地址:
收件人姓名:
收件人电话:
收件人地址:
订单总重量(kg):
[微信/支付宝/现金]支付金额:
货物明细如下:
明细编号 货物名称 计费重量 计费费率 应交运费
1 ...
2 ...
注:输出中实型数均保留1位小数。
输入样例:
在这里给出一组输入。例如:
Corporate
10001
郭靖
13807911234
南昌航空大学
Expedite
2
101
发电机
80
60
40
80
102
信号发生器
55
70
60
45
MU1234
昌北国际机场
大兴国际机场
2025-04-22
1000
900001
2025-04-22
南昌大学
洪七公
18907912325
北京大学
黄药师
13607912546
ALiPay
输出样例:
在这里给出相应的输出。例如:
客户:郭靖(13807911234)订单信息如下:
航班号:MU1234
订单号:900001
订单日期:2025-04-22
发件人姓名:洪七公
发件人电话:18907912325
发件人地址:南昌大学
收件人姓名:黄药师
收件人电话:13607912546
收件人地址:北京大学
订单总重量(kg):125.0
支付宝支付金额:4360.0
货物明细如下:
明细编号 货物名称 计费重量 计费费率 应交运费
1 发电机 80.0 40.0 3200.0
2 信号发生器 45.0 50.0 2250.0
两次迭代的题目分析
一、输入输出的变化
客户类型细分
第一题客户类型没区分,第二题要输入客户类型(个人 / 企业)。
货物类型新增
货物类型多了Normal/Expedite/Dangerous(普通 / 加急 / 危险品)。这意味着不同类型货物的计费规则不一样。
支付方式明确
第一题默认微信支付,第二题要输入支付方式(微信 / 支付宝 / 现金),输出时要显示对应的支付方式名称,比如 “支付宝支付金额”。
二、计费逻辑的变化
费率规则调整
第一题费率按计费重量分档(比如 < 20kg 是 35,20-50 是 30),第二题的费率和货物类型挂钩。比如样例中的加急货物(Expedite),发电机 80kg 对应费率 40 元 /kg,信号发生器 45kg 对应 50 元 /kg,和第一题同重量的费率不一样,费率规则更复杂了,需要根据货物类型和重量双重条件计算。
三、类设计的调整点
客户类需要区分类型
第一题客户类可能不用区分类型,第二题需要在Customer类里加一个customerType属性,存储 “Individual” 或 “Corporate”。
货物类新增类型属性
Goods类要加一个goodsType属性,记录货物类型(Normal/Expedite/Dangerous),然后在计算费率时,根据类型和重量一起决定费率。
费率计算逻辑复杂化
第一题Rate类可能只根据重量计算,第二题需要同时判断货物类型和重量。比如:
普通货物(Normal)按原规则(<20kg=35,20-50=30);
加急货物(Expedite)每个重量段费率更高(如 < 20kg=40,20-50=50);
危险品(Dangerous)有更高费率。
这时候Rate类的calculateRate方法需要接收货物类型参数,或者在货物类中根据类型调用不同的费率计算逻辑。
四、其他细节变化
输出中的支付方式
第一题固定 “微信支付”,第二题要根据输入显示 “微信 / 支付宝 / 现金”,所以输出语句需要动态获取支付方式名称,不能写死。
输入顺序调整
客户信息里多了 “客户类型” 和 “货物类型” 的输入,顺序要注意。比如第二题输入顺序是:客户类型→客户编号→姓名→电话→地址→货物类型→运送数量→货物详情……,别搞错顺序导致输入错位。
五、总结:
难点在哪?
第二题比第一题复杂的地方主要在于多了 “类型” 维度的判断:客户类型、货物类型都会影响业务逻辑。需要在代码里处理类型的枚举值,并且在计算费率时增加条件判断,可能需要用if-else或者switch根据货物类型分情况处理。另外,输入验证也要注意类型是否合法(比如货物类型只能是那三个选项),可能需要加校验逻辑。
类图如下
类的分析如下
抽象基类
People类:封装姓名、电话、地址等公共属性,为Customer、Sender、Recipient提供统一基类。
核心业务类
Customer类:
属性:客户类型(个人 / 企业)、手机号、货物列表。
方法:Discount()根据客户类型计算折扣,add()添加货物,display()输出客户信息。
Good类:
属性:类型(普通 / 加急 / 危险)、编号、尺寸、重量。
方法:displaygrand()静态输出表头,price()计算运费,compareTo()比较重量,Judgement()判断货物类型,Rate()获取费率。
Plane类:
属性:航班号、时间、日期、最大载重量、货物列表。
方法:add()添加货物,check()校验载重,allweight()计算总重,price()计算总运费,display()输出航班信息。
Order类:
属性:订单号、日期、支付方式。
方法:Judgement()判断支付方式,displaypayment()输出支付信息,display()输出订单详情。
Sender/Recipient类:继承People,重写display()输出发件人 / 收件人信息。
代码复杂度分析
代码行数与语句数:
代码文件有 370 行,语句数为 98 条。相较于常见规模的代码,行数不算特别少,语句数占比约为 26.5%(98÷370 ) ,说明代码里可能存在不少空行、声明等非执行语句内容。
可以适当整理,减少不必要的空行,让代码结构更紧凑。
类和方法数量:
类和接口数量为 8 个,平均每个类有 3.75 个方法。整体上代码有一定的模块化程度,通过多个类来实现不同功能,但可能部分类的职责划分还可以进一步优化,避免出现个别类方法过多或过少的情况。
代码复杂度相关指标
分支语句占比:
分支语句百分比(Percent Branch Statements )为 12.2% ,比之前有所增加。这意味着代码中条件判断(如if - else、switch )的逻辑更多了。适当的分支语句能实现灵活的逻辑控制,但过多可能导致代码逻辑复杂,可读性变差,需要注意梳理条件判断的逻辑,避免出现过于复杂的嵌套。
方法调用情况:
方法调用语句数(Method Call Statements )只有 12 条,相对较少。代码中各个类之间的交互协作还不够充分,或者部分功能的实现没有充分利用方法调用进行模块化拆分,有些功能可能在一个方法里集中实现了。
最复杂方法:
最复杂方法是DangerousCargoRate.calculateRate() ,行号为 74 。这是危险货物费率计算方法,它复杂可能是因为危险货物的费率计算规则比较复杂,涉及到多个重量区间或者特殊的计算条件。可以考虑对这个方法进行拆分和重构,把不同的计算逻辑拆分成小方法,提高代码的可读性和可维护性。
注释情况
带注释的代码行百分比(Percent Lines with Comments )为 3.5% ,注释依旧非常少。这对代码的后期维护和理解极为不利,无论是自己过段时间再看代码,还是其他开发人员接手,都很难快速明白代码的意图和功能。需要及时添加注释,对关键类、方法、代码段进行说明。
可视化图表解读
Kiviat Graph(雷达图):
从图中可以看到各项指标分布不均匀。比如 “Max Complexity(最大复杂度)”“Max Depth(最大深度)” 这些指标超出理想范围,说明代码里存在一些复杂度较高的部分,和前面提到的最复杂方法相呼应。“% Comments(注释百分比)” 这块数值很低,再次强调了注释严重不足的问题。
Block Histogram(柱状图 - 语句数与深度):
可以看到在深度为 1 和 2 的地方语句数较多,说明大部分代码的逻辑层次不算特别深,但也存在一些深度更高的部分,这些地方可能就是代码中比较复杂的逻辑所在,需要重点关注和优化。
改进建议
优化代码结构:
进一步梳理类与类之间的关系,合理分配方法,让每个类的职责更加单一明确。对于方法调用较少的情况,思考如何更好地进行模块化拆分,提高代码的复用性和可维护性。
简化复杂方法:
针对DangerousCargoRate.calculateRate()这样复杂的方法,按照不同的计算逻辑拆分成多个小方法,每个小方法负责一个具体的计算步骤或者条件判断,提高代码的可读性。
增加注释:
对每个类的功能、每个方法的作用和参数含义,以及关键代码段的逻辑,都添加详细的注释。方便自己和他人后期理解和维护代码。
检查分支逻辑:
对于占比 12.2% 的分支语句,仔细检查条件判断的逻辑,避免不必要的嵌套和复杂的条件组合,让代码逻辑更加清晰易懂。
小结
这次航空货运管理系统题目,因对题目理解偏差、代码逻辑与类设计不足做错。经上网查阅资料,发现逻辑瑕疵与语法问题才做对。今后会吃透题目,优化代码设计,注重细节。
踩坑心得
一、逻辑设计的 “想当然” 陷阱
在第一题的初始设计中,我想当然地认为 “计费重量” 仅需比较毛重和体积重量的数值大小,却忽略了题目中隐含的单位转换逻辑。
踩坑场景:直接使用输入的尺寸数值计算体积重量,未确认单位是否需统一(如题目默认单位为厘米,但代码中未做说明),导致样例测试时运费计算结果与预期不符。
心得:业务逻辑需严格对照题目描述逐字验证,避免用 “常识” 替代需求。对于公式类计算应在代码注释中明确规则来源,并通过单元测试验证边界值(如重量为 0、尺寸为 0 的非法输入)。
二、类设计的职责混乱
在第二题的迭代中,我尝试用抽象类和接口重构代码,但因职责划分不清晰,导致Goods类同时承担 “计算费率” 和 “输出格式” 的双重职责。
踩坑场景:Goods类的display()方法中混入了运费计算逻辑,违反单一职责原则。当题目要求新增 “危险品附加费” 时,需修改Goods类的多个方法,代码扩展性极差。
心得:类设计应遵循 “单一职责”,将 “数据计算”(如费率、重量)与 “数据展示”(如输出格式)分离。例如,单独创建RateCalculator类处理费率逻辑,OrderPrinter类负责输出,避免牵一发而动全身。
三、输入输出格式的 “魔鬼细节”
两道题中,输出格式的精度控制和对齐要求成为最大 “拦路虎”。
踩坑场景:
第一题中未使用printf("%.1f")控制小数位数,导致输出结果为整数(如 “125.0” 显示为 “125”),与样例格式不一致。
第二题的货物明细表格中,字段间距未用制表符\t统一,出现 “货物名称” 与 “计费重量” 错位的情况。
心得:输出格式需严格对照样例调试,建议先在控制台打印固定字符串模拟格式,再逐步填充动态数据。对于表格类输出,可先用文本编辑器编写模板,确认对齐方式后再转化为代码。
四、多态与继承的 “过度设计”
为实现第二题的 “客户类型折扣” 功能,我错误地通过继承User类的方式区分个人用户和企业用户,导致子类中存在大量重复代码。
踩坑场景:IndividualUser和CorporateUser类仅applyDiscount()方法不同,但其他属性和方法完全一致,违背 “组合优于继承” 原则。
心得:对于行为差异小的场景,优先使用策略模式(如定义DiscountStrategy接口,通过组合方式注入User类),而非继承。这样可避免子类膨胀,更灵活地扩展新折扣策略(如限时折扣、会员折扣)。
总结:从 “功能实现” 到 “健壮性优化”
这两道题让我深刻体会到:编程不仅是实现功能,更是对 “细节” 和 “可维护性” 的打磨。未来需养成 “先设计类图、再编写代码” 的习惯,通过模块化测试验证每个功能点,并预留扩展接口(如抽象类、策略模式),避免因需求变更导致代码大面积重构。这次的大作业让我明白了一个道理:代码能跑只是起点,能优雅地应对变化才是终点!!