NCHU OOP BLOG-2
目录
1.前言
2.设计与分析
2.1. practice 1
2.1.1.题目要求:
2.1.2.题目需求分析
2.1.3.算法设计
2.1.4.算法实现步骤
2.1.5.SourceMonitor分析
2.2. practice 2
2.2.1.题目要求:
2.2.2.题目需求分析
2.2.3.算法设计
2.2.4.算法实现步骤
2.2.5.SourceMonitor分析
3.踩坑心得
4.改进建议
5.总结
1.前言
已经是第二次使用Blog记录我的OOP题目集
或许对于我而言,处理这个已经不算陌生了。在一次次运行程序后,我的编码水平也慢慢随之提高了。OOP9基于OOP9的运费计算系统进行迭代开发,重点在于扩展功能与优化架构。本次作业要求支持多种货物类型(普通、加急、危险品)的运费计算,并引入客户类型与支付方式的差异化处理。与第一次作业相比,复杂度没有提升很多,更多是类设计,需要更精细的类设计和更灵活的策略模式应用。
知识点:
OOP8:面向对象基础原则、类设计、订单处理与打印逻辑、基本输入输出解析。
OOP9:多态与接口设计、客户类型与支付方式扩展、代码分层优化。
题目数量和难度相较于之前降低了不少。
2.设计与分析
2.1. practice 1
2.1.1.题目要求:
某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包 括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。 本次作业主要考虑货物重量/体积,以下是具体的计算方式和关键要点:
一、计费重量的确定 空运以实际重量(GrossWeight)和体积重量(VolumeWeight)中的较 高者作为计费重量。 计算公式: 体积重量(kg)= 货物体积(长×宽×高,单位:厘米)÷6000 示例: 若货物实际重量为80kg,体积为120cm×80cm×60cm,则: 体积重量 =(120×80×60)÷6000=96kg 计费重量取96kg(因96kg>80kg)。
二、基础运费计算 费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如 CNY 30/kg)。本次作业费率采用分段计算方式:公式:基础运费 = 计费重量 × 费率
三、题目说明 本次题目模拟某客户到该航空公司办理一次货运业务的过程: 航空公司提供如下信息: 航班信息(航班号,航班起飞机场所在城市,航班降落机场所在城市,航班 日期,航班最大载重量) 客户填写货运订单并进行支付,需要提供如下信息: ⎫ 客户信息(姓名,电话号码等) ⎫ 货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等) ⎫ 运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选 航班号,订单日期) ⎫ 支付方式(支付宝支付、微信支付) 注:一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独 计费
输入格式:
按如下顺序分别输入客户信息、货物信息、航班信息以及订单信息。
客户编号
客户姓名
客户电话
客户地址
运送货物数量
[货物编号
货物名称
货物宽度
货物长度
货物高度
货物重量
]//[]内的内容输入次数取决于“运送货物数量”,输入不包含“[]”
航班号
航班起飞机场
航班降落机场
航班日期(格式为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位小数。
2.1.2.题目需求分析
- 计费重量计算:
实际重量与体积重量取较大值。
体积重量公式:
体积重量(kg)=长(cm)×宽(cm)×高(cm)6000体积重量(kg)=6000长(cm)×宽(cm)×高(cm)
示例:货物实际重量 80kg,体积重量 96kg → 计费重量为 96kg。
- 基础运费计算:
分段费率:根据计费重量范围动态确定费率(如 <20kg 为 35 元/kg)。
公式:
基础运费=计费重量×费率基础运费=计费重量×费率
- 订单处理与输出:
输入数据:
客户信息(编号、姓名、电话、地址)。
货物信息(多件货物的名称、尺寸、重量)。
航班信息(航班号、起降城市、日期、最大载重量)。
订单信息(订单号、日期、发件人/收件人信息、支付方式)。
输出要求:
超载时提示航班无法承接订单。
正常时输出订单明细,包括客户信息、航班号、货物明细、总重量及支付金额,保留 1 位小数。
2.1.3.算法设计
核心类与职责划分
- 策略接口FreightCalculator:
定义运费计算的核心方法:
calculateChargeableWeight:计算计费重量(体积与实际重量取较大值)。
determineRate:根据计费重量确定分段费率。
calculateBaseFreight:计算单件货物的基础运费。
实现类StandardFreightCalculator:
实现标准运费策略,费率分段为:
<20kg: 35元/kg
20-50kg: 30元/kg
50-100kg: 25元/kg
≥100kg: 15元/kg
订单处理类OrderProcessor:
输入:货物数组、航班信息。
职责:遍历货物,累加总计费重量和总运费。
判断总重量是否超过航班最大载重。
输出:封装处理(总重量、总运费、超载标志)。
订单打印类OrderPrinter:
输入:客户、货物、航班、订单、处理结果。
职责:
若超载,输出提示信息。
若正常,按格式输出订单明细(客户信息、航班号、货物表格等)。
数据类:
Passenger、Goods、Plane、Order:仅用于存储输入数据,不包含业务逻辑。
类图如下

2.1.4.算法实现步骤
输入解析
客户信息:依次读取客户编号、姓名、电话、地址。
货物信息:读取货物数量,循环读取每件货物的编号、名称、尺寸、重量。
航班信息:读取航班号、起飞机场、降落机场、日期、最大载重量。
订单信息:读取订单编号、日期、发件人/收件人信息。
运费计算
初始化运费策略:使用 StandardFreightCalculator。
遍历货物:对每件货物调用calculateChargeableWeight和calculateBaseFreight。
累加结果:统计总重量和总运费,判断是否超载。
结果输出
超载处理:直接输出航班超载提示。
正常输出:
打印客户信息、航班号、订单号等头部信息。
输出每件货物的明细(计费重量、费率、运费)。
显示总重量和微信支付金额(固定为总运费,无折扣)。
2.1.5.SourceMonitor分析

从这份代码分析结果瞧,本次的结构没啥复杂的,也履行着单一职责原则,还有注释比例(21.7%),整体挺规整,以后自己或别人瞅两眼也能明白咋回事。复杂度也低,分支语句才占 3.1%,没啥深坑嵌套,维护起来不费劲。不过嘛,有些地方还能再捯饬捯饬。比如平均每个类塞了 14 个方法,像OrderPrinter这种类活儿太多,回头得拆开分分工。总之,地基打得稳,往后加功能改代码才能顺,不担心一动全塌喽!
2.2. practice 2
2.2.1. 题目要求:
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场,航班降落机场,航班日期,航班最大载 重量)
2 客户填写货运订单并进行支付,需要提供如下信息:
⎫ 客户信息(姓名,电话号码等)
⎫ 货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
⎫ 运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选 航班号,订单日期)
⎫ 支付方式(支付宝支付、微信支付、现金支付)
注:一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独 计费
计算公式:基础运费 = 计费重量 × 费率 × 折扣率 其中,折扣率是指不同的用户类型针对每个订单的运费可以享受相应的折扣, 在本题中,用户分为个人用户和集团用户,其中个人用户可享受订单运费的9 折优惠,集团用户可享受订单运费的8折优惠
2.2.2. 题目需求分析:
货物类型与费率动态调整
普通货物:
35 重量<20
30 20≤重量<50
25 50≤重量<100
15 重量≥100
危险货物:
80 重量<20
50 20≤重量<50
30 50≤重量<100
20 重量≥100
加急货物:
60 重量<20
50 20≤重量<50
40 50≤重量<100
30 重量≥100
客户类型与折扣率
个人用户:订单运费享受9 折优惠。
集团用户:订单运费享受8 折优惠。
支付方式扩展
支持支付宝支付、微信支付、现金支付,并在输出中显示中文名称。
输入数据新增字段
客户信息:需区分客户类型(个人/集团)。
货物信息:需指定货物类型(普通/危险/加急)。
订单信息:需指定支付方式(微信/支付宝/现金)。
输出内容扩展
支付金额:显示折扣后的金额,例如:
若为集团用户,显示支付宝支付金额:XXX;
若为个人用户,显示微信支付金额:XXX。
2.2.3. 算法设计:
策略接口FreightCalculator
职责:定义运费计算的核心逻辑。
关键方法:
calculateChargeableWeight:计算计费重量(体积与实际重量取较大值)。
determineRate:根据计费重量确定分段费率。
calculateBaseFreight:计算单件货物的基础运费。
实现类:
NormalFreightCalculator:普通货物费率
ExpediteFreightCalculator:加急货物费率
DangerousFreightCalculator:危险货物费率
订单处理类OrderProcessor
输入:货物数组、航班信息。
职责:
遍历货物,累加总计费重量和总运费。
判断总重量是否超过航班最大载重。
输出:封装输出(总重量、总运费、超载标志)。
订单打印类OrderPrinter
输入:客户、货物、航班、订单、处理结果。
职责:
若超载,输出提示信息。
若正常,按格式输出订单明细(客户信息、航班号、货物表格等)。
动态显示支付方式(微信、支付宝、现金)及客户折扣(个人9折,企业8折)。
数据类
Customer:存储客户类型(个人/企业)、姓名、电话等信息。
Goods:存储货物尺寸、重量等属性。
Plane:存储航班号、最大载重量等。
Order:存储订单信息,包括支付方式。
类图如下

2.2.4. 算法实现步骤:
输入解析
读取客户信息:依次输入客户类型、编号、姓名、电话、地址。
读取货物信息:
输入货物类型(普通/加急/危险)。
根据货物数量循环读取每件货物的编号、名称、尺寸、重量。
读取航班信息:航班号、起飞机场、降落机场、日期、最大载重量。
读取订单信息:订单编号、日期、发件人/收件人信息、支付方式。
根据输入的货物类型,使用对应的运费标准:
switch (goodsType) {
case "Normal": calculator = new NormalFreightCalculator(); break;
case "Expedite": calculator = new ExpediteFreightCalculator(); break;
case "Dangerous": calculator = new DangerousFreightCalculator(); break;
default: throw new IllegalArgumentException("Invalid goods type");
}
运费计算与超载判断
遍历货物:
调用calculateChargeableWeight计算每件货物的计费重量。
调用calculateBaseFreight计算每件货物的基础运费。
累加结果:统计总重量和总运费。
超载判断:若总重量超过航班最大载重,标记为超载。
结果输出
超载输出:直接输出航班超载提示。
正常输出:
打印客户信息、航班号、订单号等头部信息。
根据客户类型计算折扣(企业8折,个人9折)。
动态映射支付方式(如“微信”对应Wechat)。
输出货物明细表格,包括计费重量、费率、应交运费,保留1位小数。
2.2.5. SourceMonitor分析:

从这个分析看,注释漏了一堆(才13.4%),也许是有点熟悉,很多地方就没有写注释,这不是一个很好的习惯,其他人不一定能看懂我的代码。分支语句由于费率计算,使用了很多分支语句,OrderPrinter类大包大揽,打印逻辑嵌套了5层,看得眼晕,要拆成算折扣、转支付、打表格三件套。总之,要补注释、砍分支、拆大类,代码才能从“凑合跑”变身“随便造”
3.踩坑心得
在本次航空货运管理系统的开发中,我深刻体会到需求理解偏差和设计模式不正确使用带来的痛苦。
输入解析健壮性不足
例子:用户输入日期格式为“2023/05/20”(应为“YYYY-MM-DD”),程序未校验直接存储,后续业务逻辑会因格式错误而错误。
教训:运行前校验是保障系统稳定的第一道防线,必须对关键字段(日期、电话号、数字范围)进行格式和范围检查。
策略模式选择失误
一开始试图在OrderProcessor里硬编码所有运费规则,结果新增货物类型时被迫重写核心逻辑。后来改用接口多态,总算实现“新增策略不改旧代码”,但初期浪费了3小时在错误设计上。
折扣计算埋雷
把折扣率计算直接写在OrderPrinter里,导致测试运费总额时被迫连打印逻辑一起跑。某次修改支付方式映射时,不小心把企业折扣率写成0.7,直到测试订单金额对不上才发现问题。
4.改进建议
用工厂模式封装运费计算器创建逻辑,把Main类里那个switch搬进FreightCalculatorFactory,这样新增货物类型时只需改工厂类。
public class FreightCalculatorFactory {
public static FreightCalculator create(String type) {
return switch (type) {
case "Normal" -> new NormalFreightCalculator();
case "Expedite" -> new ExpediteFreightCalculator();
case "Dangerous" -> new DangerousFreightCalculator();
default -> throw new IllegalArgumentException("未知货物类型: " + type);
};
}
}
把OrderPrinter拆成HeaderPrinter、PaymentPrinter、GoodsTablePrinter三个小弟,每个类专注一块输出:
class HeaderPrinter {
void print(Customer customer) {
System.out.printf("客户:%s(%s)订单信息如下:%n", customer.getName(), customer.getTele());
}
}
5.总结
这次作业让我真正体会到了设计模式的力量。如果要增加一个货物类型时,可以通过实现FreightCalculator接口完成需求,也不需要更改其他代码,这是独属于开闭原则的魅力。把输入解析、业务计算、结果输出分离后,找bug效率提升50%,再也不用在上百行代码里大海捞针。而且相比于上次大作业,我觉得我的状态已经好很多了,没有像之前那样手忙脚乱了。代码质量也一步步提高,遇到复杂问题也能更有条理地解决了。这次项目让我明白,类设计和继承多态要搞清楚,设计要合理,规范代码书写很重要。
浙公网安备 33010602011771号