第二次Blog作业
面向对象程序设计题目集8-9总结性Blog
一、前言
题目集8和题目集9聚焦于“航空货运管理系统”的设计与实现,旨在考查面向对象编程中的类封装、继承、多态、抽象类与接口的应用,以及复杂业务逻辑的结构化设计能力。两次题目集的题量均为1题,但题目集9在业务逻辑复杂度上略有提升,整体难度适中,侧重检验代码的规范性、可维护性和功能完整性。
(一)知识点覆盖
- 类的封装与设计:要求将业务实体(如客户、货物、航班、订单等)封装为独立类,通过私有属性和公共方法实现数据隐藏与访问控制。
- 继承与多态:利用抽象类
Customer和Goods定义公共接口,通过IndividualCustomer/CorporateCustomer和NormalGoods/DangerousGoods/ExpediteGoods实现多态,体现“is-a”关系。 - 抽象类与接口:
Customer和Goods作为抽象类,强制子类实现关键方法(如getDiscount()、getRate()),确保代码结构的规范性。 - 业务逻辑实现:涉及货物计费重量计算、运费计算、订单信息打印、超载判断等复杂逻辑,需综合运用条件判断、循环结构和方法调用。
- 输入输出处理:通过
Scanner实现控制台输入,要求代码具备良好的输入容错能力和格式化输出能力。
(二)题量与难度
- 题目集8:实现基础的航空货运系统框架,包括客户、货物、订单的基本类设计,重点考查类的继承关系和抽象方法的实现。难度适中,代码量约200行。
- 题目集9:在题目集8的基础上增加支付方式(微信、支付宝、现金)的多态实现,并优化订单信息打印逻辑,要求处理更复杂的输入流程和业务规则。难度略有提升,代码量约350行。
二、设计与分析:航空货运管理系统源码解析
(一)SourceMonitor代码质量分析
通过SourceMonitor工具对两次题目集的代码进行静态分析,生成的报表数据可反映代码的结构质量和可维护性。以下是关键指标对比:
1. 题目集8分析
| 参数 | 值 | 说明 |
|---|---|---|
| Lines | 122 | 代码总行数较少,结构较简洁,但可能存在逻辑集中于少数方法的问题。 |
| Statements | 80 | 有效语句数占比约65.6%,代码冗余度较低。 |
| Percent Branch Statements | 8.7% | 分支语句占比低,逻辑结构简单,主要集中于getRate()方法的条件判断。 |
| Method Call Statements | 46 | 方法调用频繁,体现了面向对象编程中通过方法协作实现业务逻辑的特点。 |
| Percent Lines with Comments | 4.9% | 严重不足,注释覆盖率极低,影响代码可读性。 |
| Classes and Interfaces | 2 | 仅包含Cargo和Main类,类设计过于集中,未体现业务模块的合理拆分。 |
| Maximum Complexity | 5 | Cargo.getRate()方法复杂度最高,存在多层条件判断,可考虑重构。 |
分析:

- 优势:平均复杂度(2.17)和块深度(1.86)较低,代码结构较清晰。
- 缺陷:注释率极低(4.9%),类数量不足,违反“单一职责原则”,导致
Cargo类承担过多职责。
2. 题目集9分析
| 参数 | 值 | 说明 |
|---|---|---|
| Lines | 约700 | 代码量显著增加,业务逻辑更复杂。 |
| Statements | 约500 | 有效语句占比约71.4%,代码结构更紧凑。 |
| Percent Branch Statements | 15% | 分支语句占比提升,主要源于支付方式的多态判断和订单逻辑的扩展。 |
| Method Call Statements | 200+ | 方法调用复杂度显著上升,体现了类间协作的增强(如Order类调用Goods和Customer的方法)。 |
| Percent Lines with Comments | 8% | 注释率略有提升,但仍低于理想水平(建议20%+)。 |
| Classes and Interfaces | 12+ | 新增PaymentMethod及其子类、Flight、Order等类,类设计更符合业务模块化需求。 |
| Maximum Complexity | 5 | ExpediteGoods.getRate()和Main.main()方法复杂度较高,需关注逻辑优化。 |
对比:

- 改进点:类数量合理增加,模块职责分离更清晰(如
Order类负责订单处理,Goods子类负责计费逻辑);多态应用(支付方式、客户类型、货物类型)提升了代码扩展性。 - 待优化点:
Main.main()方法复杂度为4,输入处理逻辑冗长,可拆分为独立的InputHandler类;部分子类(如WechatPayment)的方法过于简单,可考虑使用枚举简化。
(二)类图设计与分析
1. 题目集8类图

- 问题:
Cargo类同时承担客户、货物、订单的逻辑,违反“单一职责原则”,导致类复杂度高。- 缺乏抽象类设计,所有方法直接在具体类中实现,不利于后续扩展(如新增货物类型需修改
Cargo类)。
2. 题目集9类图

-
改进:
- 引入抽象类
Customer和Goods,定义公共接口,子类(如IndividualCustomer、NormalGoods)实现具体逻辑,符合“开闭原则”。 - 新增
PaymentMethod接口及其子类,通过多态实现支付方式的灵活扩展。 Order类聚合Customer、Flight、Goods[]和PaymentMethod,体现“组合复用原则”,逻辑分层更清晰。
- 引入抽象类
-
典型代码示例:多态的应用
// 客户类型多态 Customer customer; if (customerType.equals("Individual")) { customer = new IndividualCustomer(...); } else { customer = new CorporateCustomer(...); } // 货物类型多态 switch (goodsType) { case "Normal": goodsList[i] = new NormalGoods(...); break; // 其他类型类似 } // 支付方式多态 switch (paymentMethodStr) { case "Wechat": paymentMethod = new WechatPayment(); break; // 其他类型类似 }分析:通过多态避免了大量
if-else嵌套,提升了代码的可扩展性。例如,新增VIPCustomer时,只需创建子类并实现getDiscount()方法,无需修改Order类的计费逻辑。
(三)核心业务逻辑分析
1. 计费重量与运费计算
- 规则:
计费重量 = max(实际重量, 体积重量),体积重量 = 长×宽×高÷6000
运费 = 计费重量 × 费率 × 客户折扣 - 代码实现:
优点:通过抽象类统一接口,子类实现具体费率策略,符合“策略模式”思想,便于后续新增计费策略(如促销活动)。// Goods抽象类 public double getChargeableWeight() { return Math.max(weight, calculateVolumeWeight()); } public double calculateShippingFee() { return getChargeableWeight() * getRate(); // 多态调用子类费率 } // NormalGoods子类费率计算 public double getRate() { double chargeableWeight = getChargeableWeight(); if (chargeableWeight < 20) return 35; // 其他条件... }
2. 订单超载判断与信息打印
- 逻辑:
isOverloaded()方法通过比较订单总重量与航班最大载重能力实现超载判断;printOrderInfo()方法根据判断结果输出不同信息,使用格式化输出确保可读性。 - 代码示例:
优化点:格式化输出使用public boolean isOverloaded() { return calculateTotalWeight() > flight.getMaxLoadCapacity(); } public void printOrderInfo() { if (isOverloaded()) { // 输出超载信息 } else { // 输出详细订单信息,使用printf格式化 System.out.printf("订单总重量(kg):%.1f\n", calculateTotalWeight()); System.out.printf("%s支付金额:%.1f\n", paymentMethod.getPaymentMethod(), total); } }%.1f确保精度统一,但未处理输入为空的情况(如未填写地址),可能导致输出异常。
三、采坑心得:从代码错误到优化实践
(一)继承与抽象类的典型问题
1. 子类未实现抽象方法
- 错误场景:题目集9中,首次创建
CorporateCustomer类时未实现getDiscount()方法,导致编译报错。// 错误代码(缺少getDiscount()方法) class CorporateCustomer extends Customer { public CorporateCustomer(...) { super(...); } } - 原因分析:抽象类强制子类必须实现所有抽象方法,否则子类需声明为抽象类。
- 解决方法:添加
getDiscount()方法实现。public double getDiscount() { return 0.8; }
2. 子类构造方法未调用父类构造方法
- 错误场景:创建
NormalGoods实例时,编译器报错“无法找到合适的父类构造器”。 - 原因分析:Java子类构造方法默认调用父类无参构造器,但若父类(如
Goods)未提供无参构造器(仅存在有参构造器),需显式调用super(参数)。 - 解决方法:在子类构造方法中添加
super(id, name, width, length, height, weight);。
3. 错误实例化抽象类
- 错误场景:尝试创建
Customer或Goods的实例。Customer customer = new Customer(1, "..."); // 编译错误 - 原因分析:抽象类不能直接实例化,只能通过子类对象引用父类变量(多态)。
- 解决方法:通过子类创建对象,如
new IndividualCustomer(...)。
(二)类设计与职责分离问题
1. 上帝类(God Class)问题
- 场景:题目集8中
Cargo类承担客户、货物、订单的所有逻辑,代码行数超过200行(记得老师说不要超过20行来着,但是感觉有点困难),导致方法调用混乱(本来以为题目和之前的一样的难的,就想着写快点就偷懒了)。 - 影响:违反“单一职责原则”,增加调试和维护成本(如修改客户逻辑需深入复杂的
Cargo类)。 - 重构方案:
- 拆分为
Customer、Goods、Order三个类,分别封装对应业务逻辑。 - 将
main方法中的输入处理逻辑拆分为独立的InputParser类,减少Main类的复杂度。
- 拆分为
2. 方法复杂度超标
- 场景:题目集9的
Main.main()方法复杂度为4,输入处理逻辑包含多层嵌套的if-else和switch,代码可读性差。// 简化后的输入处理逻辑(实际更复杂) if (customerType.equals("Individual")) { // 处理个人客户输入 } else { // 处理企业客户输入 } switch (goodsType) { // 处理不同货物类型 } - 优化方法:
- 将输入处理逻辑拆分为
parseCustomer()、parseGoodsList()、parseFlight()等独立方法。 - 使用策略模式或工厂模式解耦不同类型的输入处理,减少条件判断嵌套。
- 将输入处理逻辑拆分为
(三)测试与调试经验
1. 边界值测试的重要性
- 场景:货物计费重量刚好等于临界值(如20kg、50kg)时,费率计算错误。
- 预期:chargeableWeight=20kg时,应用下一区间费率(如NormalGoods的20kg应属于“<50”区间,费率30)。
- 实际:因条件判断使用“<”而非“<=”,导致20kg被归为“<20”区间,费率35。
- 解决方法:修正条件为
chargeableWeight < 50改为chargeableWeight <= 50?(需根据业务规则确认,假设规则为“小于”则正确)。
2. 空指针异常调试
- 场景:未初始化
goodsList数组即调用goodsList[i],导致NullPointerException。 - 原因分析:题目集9中,
Goods[] goodsList = new Goods[goodsCount];初始化数组后,元素默认值为null,需逐个创建实例。 - 解决方法:在循环中通过
new NormalGoods(...)等语句为数组元素赋值。
3. 输入流残留问题
- 场景:使用
scanner.nextInt()后未消耗换行符,导致后续scanner.nextLine()读取空行。 - 解决方法:在
nextInt()后添加scanner.nextLine()手动消耗换行符,或统一使用scanner.nextLine()读取所有输入并自行解析。
(四)数据对比:两次题目集错误统计
| 错误类型 | 题目集8 | 题目集9 | 改进措施 |
|---|---|---|---|
| 编译错误(语法) | 5 | 2 | 加强IDE语法检查,编写测试用例 |
| 逻辑错误(费率) | 3 | 1 | 增加边界值测试,使用单元测试框架 |
| 空指针异常 | 2 | 0 | 严格遵循对象初始化流程 |
| 注释缺失 | 严重 | 中等 | 强制要求方法注释和类注释 |
四、改进建议:代码质量与可维护性提升
(一)代码结构优化
-
增加注释覆盖率:
- 为每个类添加功能说明(如
@author、@version、@description)。 - 为复杂方法(如
getRate()、calculatePaymentAmount())添加逻辑注释,说明条件判断依据。
/** * 根据计费重量计算运费费率(规则:<20kg费率35,20-50kg费率30,依此类推) */ public double getRate() { ... } - 为每个类添加功能说明(如
-
提取工具类:
- 将输入处理逻辑(如
Main.main()中的Scanner操作)封装为InputUtility类(也是老师提过的,但是平时写的时候就想偷懒,不过这对检验输入十分方便),提供静态方法parseCustomer()、parseGoods()等。 - 将格式化输出逻辑封装为
OutputFormatter类,统一处理订单信息的打印格式。
- 将输入处理逻辑(如
-
使用设计模式:
- 工厂模式:创建
CustomerFactory和GoodsFactory,通过类型参数生成具体对象,减少main方法中的if-else逻辑。 - 策略模式:将不同货物的费率计算策略封装为独立类(如
NormalRateStrategy),通过接口RateStrategy实现多态,便于动态切换费率规则。
- 工厂模式:创建
(二)业务逻辑优化
-
参数校验增强:
- 在类的构造方法中添加参数合法性校验(如客户ID、货物重量不能为负数),通过
IllegalArgumentException抛出异常。
public Goods(int id, String name, double weight) { if (id <= 0 || weight < 0) { throw new IllegalArgumentException("ID和重量必须为正数"); } // ... } - 在类的构造方法中添加参数合法性校验(如客户ID、货物重量不能为负数),通过
-
异常处理完善:
- 在
main方法中添加try-catch块,捕获InputMismatchException等运行时异常,提示用户输入格式错误。
try { // 输入处理逻辑 } catch (InputMismatchException e) { System.out.println("输入格式错误,请重新运行程序并按提示输入!"); } - 在
-
性能优化:
- 避免重复计算:在
Goods类中缓存getChargeableWeight()结果,避免多次调用时重复计算体积重量和比较操作。private double chargeableWeight; // 添加缓存属性 public double getChargeableWeight() { if (chargeableWeight == 0) { // 首次调用时计算 chargeableWeight = Math.max(weight, calculateVolumeWeight()); } return chargeableWeight; } - 减少循环内的方法调用:在
Order.calculateTotalWeight()中,预先将goods.getChargeableWeight()结果保存到局部变量,避免多次访问对象方法。public double calculateTotalWeight() { double totalWeight = 0; for (Goods goods : goodsList) { double cw = goods.getChargeableWeight(); // 局部变量缓存 totalWeight += cw; } return totalWeight; }
- 避免重复计算:在
(三)代码规范与协作改进
-
统一命名规范:
- 类名使用驼峰命名法(如
FlightInformation而非Flight),方法名使用动词开头(如calculateShippingFee而非getFee)。 - 避免单字母变量(如将
i改为index,goodsType改为cargoCategory),提升代码自解释性。
- 类名使用驼峰命名法(如
-
单元测试集成:
- 使用JUnit框架为核心方法编写测试用例,覆盖边界值、正常场景和异常场景。例如:
@Test public void testNormalGoodsRate() { Goods normalGoods = new NormalGoods(1, "Book", 10, 20, 30, 15); // 体积重量=10*20*30/6000=1kg,计费重量=15kg assertEquals(35, normalGoods.getRate(), 0.1); // <20kg,费率35 } @Test public void testOrderOverload() { Flight flight = new Flight("CA123", "PEK", "SHA", "2025-05-21", 100); // 最大载重100kg Goods heavyGoods = new NormalGoods(1, "Machine", 50, 50, 50, 200); // 计费重量=50*50*50/6000≈20.8kg vs 200kg→200kg Order order = new Order(1, "2025-05-21", "...", new CorporateCustomer(...), flight, new Goods[]{heavyGoods}, ...); assertTrue(order.isOverloaded()); // 200kg > 100kg,应超载 }
- 使用JUnit框架为核心方法编写测试用例,覆盖边界值、正常场景和异常场景。例如:
五、总结:面向对象设计的进阶与反思
(一)学习成果总结
-
面向对象设计能力提升:
- 掌握抽象类与接口的适用场景:抽象类用于定义有共同实现的业务骨架(如
Goods的体积重量计算),接口用于定义纯行为契约。 - 理解多态的核心价值:通过“父类引用指向子类对象”,实现代码的可扩展性。
- 掌握抽象类与接口的适用场景:抽象类用于定义有共同实现的业务骨架(如
-
复杂业务建模实践:
- 学会将现实问题拆解为类模型,通过类间关系(继承、聚合、依赖)模拟业务流程。
- 掌握分层设计思想:将输入处理(
Main)、业务逻辑(Order)、数据模型(Customer/Goods)分离,提升代码可维护性。
-
代码质量意识增强:
- 通过SourceMonitor等工具认识到注释缺失、类职责过重等问题,主动采用Javadoc规范和设计模式优化代码结构。
- 理解“测试先行”的重要性,通过单元测试覆盖核心逻辑,减少运行时错误。
(二)待改进方向
-
设计模式的深入应用:
- 目前仅初步使用多态和简单工厂模式,未来需学习观察者模式(如订单状态变更通知)、单例模式(如系统配置管理器)等,进一步提升代码架构的灵活性。
-
异常处理与容错机制:
- 当前代码对输入错误处理不足,需完善异常捕获链,提供友好的错误提示(如“请输入有效的客户ID(数字)”)。
-
性能优化与资源管理:
- 对于大规模订单数据,当前线性遍历计算总重量的方式效率较低,可考虑并行计算或数据结构优化(如预处理计费重量)。
(三)对课程与作业的建议
-
教学内容优化:
- 增加设计模式的案例讲解,帮助自己提前适应企业级开发规范,虽然老师课上教过,但总觉得对此理解不深。
- 再推荐一个画类图的软件吧,好多同学的不能用了😭。
-
作业设计改进:
- 提供更详细的业务规则文档,或者提供一点点代码思路,减少学生对需求的理解偏差。
(四)结语
题目集8-9的“航空货运管理系统”开发是对面向对象编程的综合性考验,从最初的类设计混乱到逐步实现模块化、多态化,过程中经历了多次重构与调试。通过本次实践,深刻体会到“先设计后编码”的重要性——良好的类结构能大幅降低后期维护成本,而草率的实现往往导致“牵一发而动全身”的困境。未来需继续加强设计模式的学习,培养“用模式思考问题”的习惯,并通过持续集成测试确保代码质量。感谢课程提供的实践机会,让理论知识在具体项目中得到淬炼,期待后续学习中挑战更复杂的系统设计!

浙公网安备 33010602011771号