第二次blog作业
面向对象程序设计题目集8-9总结:航空货运管理系统的设计与实践
前言:从基础到扩展的进阶之旅
在面向对象程序设计的学习进程中,题目集8与9围绕“航空货运管理系统”展开,如同逐级搭建的阶梯,引领我们在代码设计与问题解决的道路上不断深入。这两次作业以航空货运的业务逻辑为核心,逐步增加功能复杂度,既考验对基础概念的掌握,又挑战对面向对象设计原则的实际应用能力。
知识点与题量分布
题目集8作为基础版本,重点聚焦于计费重量的计算与基础运费的逻辑实现。
核心知识点包括:
1、计费重量取实际重量与体积重量的最大值,计算公式为`体积重量=长×宽×高÷6000;
2、普通货物的分段费率计算,根据计费重量分为四档(如重量≥100kg时费率为15元/kg);
3、订单信息的输入输出处理,涉及客户、货物、航班等数据的结构化存储。
题目集9则在此基础上进行了大幅扩展,新增知识点包括:
1、货物类型的多元化:引入危险货物、加急货物,不同类型对应独立的分段费率表;
2、用户类型与折扣机制:个人用户享9折、集团用户享8折,需在订单总运费中应用折扣;
3、支付方式的扩展:从单一微信支付增加至支付宝、现金支付,需根据不同方式格式化输出;
4、枚举类型的应用:通过UserType、GoodsType、PaymentMethod枚举规范输入项,增强代码健壮性。
从题量上看,两次作业均要求实现完整的输入输出流程,包括客户信息、多件货物信息、航班信息、订单信息的顺序输入,以及订单报表和货物明细的格式化输出。题目集9因新增功能点,代码量约增加30%,主要体现在枚举定义、条件判断逻辑(如货物类型对应的费率计算)和折扣计算模块。
难度与挑战
题目集8的难点在于逻辑流程的清晰性:需确保从输入到输出的每个步骤(如计费重量计算、费率匹配)顺序正确,尤其要注意体积重量与实际重量的比较逻辑。而题目集9的复杂性显著提升,主要挑战包括:
1、多条件分支处理:货物类型、用户类型、支付方式的组合导致代码中出现多层switch-case或if-else嵌套,需避免逻辑混乱;
2、面向对象设计原则的应用:题目明确要求考核单一职责、里氏代换等原则,需确保类的职责划分清晰,例如Goods类仅负责货物相关计算,Order类专注于订单逻辑,避免功能混杂;
3、输入验证与异常处理:枚举类型的输入需严格匹配预设值(如Individual、Expedite),否则会导致程序崩溃,需在代码中增加容错机制。
总体而言,两次作业形成了“基础功能实现→复杂业务扩展”的递进关系,既巩固了面向对象编程的基础语法,又迫使我们深入思考如何通过合理的类设计提升代码的可维护性与扩展性。
设计与分析:从代码结构看设计原则的落地
题目集8:基础功能的结构化实现
代码结构与类设计
Customer类:存储客户基本信息(编号、姓名、电话、地址),职责单一,符合单一职责原则;
Flight类:管理航班信息(航班号、起降机场、日期、最大载重量),包含canCarry方法检查载重是否超限,addWeight方法更新当前载重,功能集中于航班状态管理;
Goods类:核心逻辑类,负责计算体积重量、计费重量及普通货物的费率。其中calculateVolumeWeight为私有方法,封装体积重量计算细节,getBillingWeight返回计费重量,getRate根据重量返回费率,体现了封装性;
Order类:整合订单相关信息(订单号、日期、客户、航班、收发件人、货物列表),计算总重量和支付金额。由于题目集8仅支持微信支付且无用户折扣,calculatePaymentAmount直接返回总运费,逻辑简单。
面向对象原则的应用与不足
单一职责原则:各主要类(Customer、Flight、Goods)职责明确,未出现功能混杂现象;
开闭原则:代码对扩展开放程度有限。例如,若新增货物类型,需直接修改Goods类的getRate方法,违反开闭原则;
合成复用原则:Order类通过组合Customer、Flight、Goods对象实现功能,而非继承,符合合成复用原则。
示例代码分析(题目集8的Goods类)
class Goods {
// 省略属性与构造方法
private double calculateVolumeWeight() {
return (width * length * height) / 6000; // 封装体积重量计算
}
public double getBillingWeight() {
double volumeWeight = calculateVolumeWeight();
return Math.max(weight, volumeWeight); // 取较大值作为计费重量
}
public double getRate() { // 普通货物费率计算
double billingWeight = getBillingWeight();
if (billingWeight < 20) return 35;
else if (billingWeight < 50) return 30;
// 省略后续条件
}}
优点:通过私有方法封装内部计算逻辑,对外暴露必要接口(getBillingWeight、getRate),符合封装性要求;
不足:费率计算硬编码在Goods类中,且仅支持普通货物,若新增货物类型需修改此类,违背开闭原则。
题目集9:扩展功能与设计原则的碰撞
代码结构的升级
新增枚举类型:
UserType(Individual/Corporate):区分用户类型,用于应用折扣;
GoodsType(Normal/Dangerous/Expedite):标识货物类型,关联不同费率表; PaymentMethod(ALiPay/Wechat/Cash):规范支付方式输入,控制输出文案。 Customer类扩展:新增userType属性,通过构造方法初始化,用于订单中的折扣计算;
Goods类重构:新增goodsType属性,getRate方法通过switch-case判断货物类型,实现多类型费率计算;
Order类增强:
新增paymentMethod属性,根据枚举值格式化支付方式名称;
calculatePaymentAmount方法引入用户折扣:集团用户(Corporate)享8折,个人用户(Individual)享9折。
设计原则的实践与问题
单一职责原则:
正面案例:Goods类负责货物属性与费率计算,Order类处理订单总逻辑,Flight类专注航班载重管理,职责清晰;
反面案例:Goods类的getRate方法包含三种货物类型的费率逻辑,代码冗长,可拆分为独立策略类(如RateStrategy接口及其实现类),以符合单一职责。
里氏代换原则:未使用继承体系,而是通过枚举和条件判断处理多类型逻辑,虽实现功能但未体现里氏代换,可通过定义Goods父类及NormalGoods、DangerousGoods等子类改进。
开闭原则:新增货物类型需修改Goods类的getRate方法,违反开闭原则;理想方案是定义RateCalculator接口,各货物类型实现该接口,通过多态实现扩展。
合成复用原则:通过枚举组合(如Goods包含GoodsType枚举)实现功能扩展,避免继承带来的紧耦合,符合合成复用原则。
示例代码对比(费率计算逻辑)
题目集8(普通货物):
public double getRate() {
if (billingWeight < 20) return 35;
// 简单条件判断
}
题目集9(多类型货物):
public double getRate() {
switch (goodsType) {
case Normal:
if (billingWeight < 20) return 35;
// ...普通货物条件
case Dangerous:
if (billingWeight < 20) return 80;
// ...危险货物条件(注意题目集1的分段为20≤H<100时费率50,H≥100时30)
case Expedite:
if (billingWeight < 20) return 60;
// ...加急货物条件(20≤H<50时50,50≤H<100时40)
}
}
问题:三种货物类型的费率逻辑集中在同一个方法中,代码复杂度高,维护成本大;
改进方向:使用策略模式,为每种货物类型创建独立的费率计算类,实现calculateRate(double weight)方法,通过多态调用,使新增货物类型时无需修改现有代码。
采坑心得:细节决定成败的实战教训
输入处理:枚举匹配与数据验证
枚举输入的严格性:题目集9要求输入的客户类型、货物类型等必须与枚举常量完全一致(如Corporate而非corporate),初期因未处理大小写问题导致IllegalArgumentException异常。解决方法:添加预处理逻辑,将输入字符串转为大写或小写后再匹配枚举值,或在代码中明确提示用户输入规范。
多件货物输入的循环控制:在读取多件货物信息时,误用scanner.nextInt()后未 consume 换行符,导致后续scanner.nextLine()读取空字符串。通过scanner.nextLine()手动读取剩余行或使用Scanner的useDelimiter方法可避免此类问题。
逻辑错误:费率分段与折扣计算
危险货物费率条件错误:题目集9中危险货物的费率分段为“20≤H<100时费率50,H≥100时30”,但初期代码误写为“20≤H<50时50,50≤H<100时30”,导致测试用例中计费错误。通过绘制费率分段表格(如下)并逐一验证边界值(如19.9kg、20kg、99.9kg、100kg),最终定位问题。
用户折扣的作用范围:题目要求折扣应用于“订单运费”,即总运费=各货物运费之和×折扣率。初期错误地将折扣应用于单件货物,导致总金额少算。通过调试Order类的calculatePaymentAmount方法,确认循环累加总运费后再应用折扣的逻辑正确性
航班载重逻辑:状态更新的时机
载重检查与更新的原子性:在题目集8中,先检查航班是否可承载订单重量(flight.canCarry(totalWeight)),若通过则调用flight.addWeight(totalWeight)更新当前载重。但初期代码中曾将这两步分开处理,导致多线程场景下可能出现载重超限(虽题目未涉及多线程,但逻辑上需保证原子性)。最终通过将检查与更新封装为carryOrder方法,确保逻辑连贯。
输出格式:小数点精度与对齐
实型数保留1位小数:使用printf("%.1f", value)时,需注意数值本身的精度问题。例如,当计算结果为整数(如2000)时,输出应为2000.0而非2000,初期因忽略此点导致格式错误。
表格对齐问题:货物明细中的字段通过制表符\t分隔,但不同字符串长度可能导致列不对齐。解决方案:使用固定宽度的格式化字符串,如%-10s表示左对齐、占10个字符宽度,确保输出美观。
改进建议:让代码更优雅的重构方向
设计模式的引入
1、策略模式(Strategy Pattern):
应用场景:将不同货物类型的费率计算、不同用户类型的折扣计算封装为策略类。
实现方案:
定义RateStrategy接口,声明double calculateRate(double weight)方法;
创建NormalRateStrategy、DangerousRateStrategy等实现类,分别实现对应费率逻辑;
在Goods类中注入RateStrategy实例,通过多态调用计算费率,避免switch-case嵌套。
优势:新增货物类型时只需创建新策略类,无需修改现有代码,完全符合开闭原则。
2、工厂模式(Factory Pattern):
应用场景:根据输入的货物类型字符串创建对应的Goods实例(或策略实例)。
实现方案:设计GoodsFactory类,其createGoods方法根据goodsTypeStr返回包含对应GoodsType枚举的Goods对象,确保输入验证与对象创建分离。
代码结构优化
拆分Goods类:将体积重量计算、费率计算等功能拆分为独立的工具类(如WeightCalculator、RateCalculator),使Goods类专注于属性存储,符合单一职责原则。
提取公共接口:为Customer、Flight、Order等类定义接口(如Billable接口声明getTotalCost()方法),规范行为契约,提升代码可替代性。
输入输出增强
异常处理机制:在关键输入点(如枚举值解析、数值转换)添加try-catch块,捕获IllegalArgumentException等异常,并提示用户重新输入,而非直接崩溃。
日志记录:在订单处理流程中添加日志输出(如“订单[900001]已创建,总重量125.0kg”),便于调试和跟踪业务流程。
测试用例完善
边界值测试:针对每种货物类型的费率边界(如普通货物19.9kg、20kg、49.9kg、50kg等)设计测试用例,确保每个费率区间的计算正确。
组合测试:覆盖用户类型(个人/集团)、货物类型(三种)、支付方式(三种)的所有组合,验证折扣与支付文案的正确性(如集团用户+加急货物+现金支付的场景)。
总结:在实践中深化对面向对象的理解
知识与技能的成长
通过这两次作业,我对面向对象编程的认知从“语法堆砌”迈向“设计思维”:
封装性:学会将复杂计算逻辑封装在类内部,通过公共方法暴露必要功能(如Goods类的getBillingWeight);
设计原则的落地:意识到单一职责是代码可维护性的基础,开闭原则是应对需求变化的关键。例如,题目集9的需求扩展迫使我思考如何避免“修改现有代码”,而通过枚举和策略模式实现“扩展新代码”;
业务逻辑的抽象能力:从航空货运的实际场景中抽象出Customer、Flight、Goods等核心实体,理清它们之间的交互关系(如订单包含客户、航班、货物),学会用类图辅助设计。
待提升的方向
设计模式的熟练度:虽然理解策略模式等的理论,但在实际编码中仍习惯使用if-else或switch-case,需通过更多项目实践强化设计模式的应用能力;
代码审查习惯:初期代码中存在重复逻辑(如订单信息输出在两次作业中重复编写),缺乏代码复用意识,需通过代码审查发现可重构点;
异常处理的全面性:当前代码假设输入完全合法,未处理如负数重量、非数字输入等极端情况,需补充健壮性检查。
对课程的建议
设计模式案例驱动教学:在课堂中增加具体场景的设计模式应用案例(如用策略模式处理费率计算),帮助学生理解如何从“面向过程”转向“面向对象设计”;
代码评审环节:组织小组间的代码互审,通过分析他人代码中的设计亮点与不足,提升对设计原则的敏感度;
渐进式需求文档:如题目集8-9的递进式设计,可进一步提供“需求变更文档”,模拟真实开发中的需求迭代,训练代码的可扩展性。
结语
航空货运管理系统的设计如同一场“代码拼图”,每个功能点都是一块碎片,而面向对象原则是指引拼图成型的图纸。从题目集8的“能跑就行”到题目集9的“兼顾设计”,过程中经历了逻辑混乱、调试崩溃的挫败,也收获了代码结构清晰、功能扩展流畅的成就感。编程之路道阻且长,唯有以设计原则为锚,在实践中不断重构与反思,才能让代码真正“活”起来,适应变化的需求浪潮。未来,我将带着这份经验,继续探索面向对象编程的更深层次,让每一行代码都蕴含思考的重量。
浙公网安备 33010602011771号