PTA题目集 8-9 总结性 Blog:航空货运管理系统的面向对象设计
一、前言
题目集 8-9 以 “航空货运管理系统” 为核心,聚焦面向对象设计原则(SOLID 原则)的综合应用,涵盖类设计、继承、多态、接口抽象、复杂业务逻辑实现等关键知识点。其中,题目集 8 要求基于基础类设计实现货运流程,重点考核单一职责、合成复用等原则;题目集 9 引入客户类型、货物类型的继承体系,新增折扣率、支付方式等扩展需求,强化里氏代换、开闭原则及依赖倒转原则的应用。
- 知识点覆盖
类设计与职责划分:需设计客户(Customer)、货物(Cargo)、航班(Flight)、订单(Order)等核心类,确保单一职责。
继承与多态:题目集 9 中客户分为个人(IndividualCustomer)与集团(CorporateCustomer),货物分为普通(NormalCargo)、加急(ExpediteCargo)、危险(DangerousCargo),通过抽象类与子类实现多态。
接口与抽象类:定义抽象方法(如 Customer.getDiscountRate ()、Cargo.getRate ())规范子类行为。
复杂业务逻辑:计费重量计算(体积重量与实际重量取最大值)、分段费率计算、折扣应用、支付方式处理等。
异常处理与流程控制:航班载重超限判断、输入数据合法性校验(题目隐含要求)。
- 题量与难度分析
题量:两次题目集均围绕同一系统迭代开发,题目集 8 为基础版本,题目集 9 为扩展版本,需在原有代码基础上新增客户 / 货物类型、支付方式等功能。
难度:
题目集 8:基础难度,重点在于类的合理拆分与职责分配,如 Cargo 类需封装计费重量与运费计算逻辑,Flight 类管理载重状态。
题目集 9:中等难度,引入继承体系后需确保里氏代换原则,如子类必须完全实现父类抽象方法;同时需处理组合关系(如 Order 包含多个 Cargo 实例),并扩展支付方式的策略模式(虽未显式使用接口,但支付方式的输出处理体现策略思想)。
二、设计与分析
(一)题目集 8:基础版本设计
- 类结构与职责分析
类图(基于 PowerDesigner 绘制)
题目集8类图

Customer 类:存储客户基础信息(编号、姓名、电话、地址),单一职责。
Cargo 类:
核心方法:calculateChargeableWeight()计算计费重量(体积重量与实际重量取最大值),calculateRate()根据重量分段确定费率,calculateFreight()计算运费。
问题:费率计算逻辑硬编码在 Cargo 类中,若新增货物类型需修改此类,违背开闭原则。
Flight 类:管理航班载重(maxLoadCapacity、currentLoad),提供addLoad()更新载重,isOverloaded()判断超限。
Order 类:组合 Cargo 实例,计算订单总重量与总运费,依赖 Flight 完成载重校验。
- 复杂度分析(SourceMonitor 数据)

分析:
Cargo 类复杂度较高,因费率计算使用多层if-else分支(如重量分段判断),可通过策略模式优化(题目集 9 改进)。
Flight 类职责单一,复杂度低,符合设计原则。
- 核心逻辑实现
// Cargo类计费重量计算
private void calculateChargeableWeight() {
double volumeWeight = (width * length * height) / 6000;
chargeableWeight = Math.max(actualWeight, volumeWeight);
}
// Order类总运费计算
public void addCargo(Cargo cargo) {
totalWeight += cargo.getChargeableWeight();
totalFreight += cargo.getFreight();
}
(二)题目集 9:扩展版本设计(继承与多态应用)
- 类结构演进
类图(基于 PowerDesigner 绘制)
题目集9类图

客户体系:
Customer抽象类:定义getDiscountRate()抽象方法(个人客户 0.9 折,集团客户 0.8 折)。
IndividualCustomer/CorporateCustomer子类:实现具体折扣逻辑。
货物体系:
Cargo抽象类:定义getRate()抽象方法,具体费率由子类实现(普通 / 加急 / 危险货物费率不同)。
NormalCargo/ExpediteCargo/DangerousCargo子类:重写费率计算逻辑。
支付方式:通过字符串标识(Wechat/ALiPay/Cash),输出时映射为中文,隐含策略模式思想(后续可扩展为接口)。
- 复杂度分析(SourceMonitor 数据)

改进点:
货物费率计算逻辑移至子类,符合开闭原则(新增货物类型只需新建子类,无需修改原有代码)。
客户折扣通过继承实现,子类可无缝替换父类,符合里氏代换原则。
- 多态与继承实现
// 抽象类Customer
public abstract class Customer {
protected String type;
public abstract double getDiscountRate();
}
// 子类CorporateCustomer
public class CorporateCustomer extends Customer {
public CorporateCustomer(...) {
super("Corporate", ...);
}
@Override
public double getDiscountRate() { return 0.8; }
}
// 多态调用(Order类计算总费用)
double totalFee = cargos.stream()
.mapToDouble(c -> c.getChargeableWeight() * c.getRate())
.sum() * customer.getDiscountRate();
三、采坑心得
- 继承体系设计缺陷(题目集 9)
问题:初始设计中Cargo子类直接复用题目集 8 的费率分段逻辑,导致代码冗余(如NormalCargo.getRate()与题目集 8 的Cargo.calculateRate()逻辑重复)。
解决方案:提取公共逻辑到抽象类,通过模板方法模式优化。
// 抽象类Cargo新增模板方法
protected double getBaseRate(double weight) {
if (weight < 20) return 35;
else if (weight < 50) return 30;
// 其他分段...
}
// 子类ExpediteCargo调用模板方法
@Override
public double getRate() {
double weight = getChargeableWeight();
return getBaseRate(weight) * 1.5; // 加急货物费率上浮50%
}
- 线程安全与数据一致性(隐含需求)
场景:多订单并发处理时,Flight 的currentLoad可能出现竞态条件(如多个订单同时修改航班载重)。
解决方案:引入synchronized关键字或AtomicDouble保证原子性(题目虽未明确多线程,但代码需具备可扩展性)。
private final AtomicDouble currentLoad = new AtomicDouble(0);
public void addLoad(double weight) {
currentLoad.addAndGet(weight);
}
- 测试用例设计不足
问题:题目集 9 中危险货物费率计算错误(如重量≥100 时费率应为 20,误写为 15),因测试用例未覆盖边界值。
测试数据补充:
货物类型 重量 (kg) 体积 (cm³) 预期计费重量 预期费率
危险货物 100 600000 100 20
加急货物 19 100000 16.666... 60
- 代码复杂度过大(题目集 8)
现象:Cargo类的calculateRate()方法v(G)= 7,违反复杂度阈值(通常建议≤5)。
优化:将费率分段逻辑封装为独立方法或枚举类。
// 枚举类RateStrategy
enum RateStrategy {
LESS_THAN_20(35), BETWEEN_20_50(30), /* 其他分段 */;
private final double rate;
RateStrategy(double rate) { this.rate = rate; }
public double getRate(double weight) { /* 返回对应费率 */ }
}
四、改进建议
- 设计模式应用
策略模式:将货物费率计算、客户折扣、支付方式处理均抽象为策略接口,消除if-else分支。
// 费率策略接口
interface RateStrategy {
double calculateRate(double weight);
}
class NormalRateStrategy implements RateStrategy { /* 实现逻辑 */ }
工厂模式:通过工厂类创建客户、货物实例,隔离客户端与具体子类。
class CustomerFactory {
public static Customer createCustomer(String type, ...) {
if ("Individual".equals(type)) return new IndividualCustomer(...);
// 其他类型...
}
}
- 代码结构优化
单一职责强化:将支付方式处理从 Order 类中拆分,独立为 PaymentService 类。
接口隔离:定义Payable接口,实现类(WechatPay、AliPay)封装支付逻辑。
interface Payable {
String getPaymentText();
double calculateFinalFee(double originalFee); // 如微信支付可能有手续费
}
- 异常处理增强
输入校验:在客户、货物、航班信息输入时,增加格式校验(如航班日期格式、货物尺寸合法性)。
自定义异常:抛出LoadExceededException、InvalidCargoException等,提升错误定位效率。
- 性能优化
缓存机制:对重复计算的计费重量、费率结果进行缓存(如同一货物多次计算时)。
集合选择:使用LinkedList存储订单货物,提升频繁插入 / 删除场景的性能(题目中为顺序添加,当前使用 ArrayList 已足够)。
五、总结
- 核心收获
SOLID 原则实践:
单一职责(SRP):确保每个类专注于单一功能(如 Flight 仅管理载重,Order 仅处理订单逻辑)。
里氏代换(LSP):子类可完全替代父类,如CorporateCustomer实例可直接用于需要Customer的场景。
开闭原则(OCP):题目集 9 新增货物类型时,通过新建子类实现,未修改原有代码。
多态与继承:通过抽象类与接口规范行为,子类实现具体逻辑,代码扩展性显著提升。
测试驱动开发(TDD):通过边界值测试(如货物重量 = 20、50、100)发现隐藏逻辑错误,确保代码鲁棒性。
- 待提升方向
设计模式深度应用:未在题目中使用工厂模式、观察者模式等,后续需在复杂场景中尝试。
代码复杂度控制:部分方法仍存在多层分支,需通过重构(如策略模式、状态模式)降低复杂度。
单元测试完善:目前测试集中于核心逻辑,缺少对异常场景(如空订单、非法输入)的覆盖。
- 对课程的建议
延续题目集 8-9 的迭代模式,在同一系统中逐步增加需求(如多航班调度、国际货运关税计算),强化系统设计的持续性。
六、结语
题目集 8-9 通过 “航空货运管理系统” 的迭代开发,系统性考察了面向对象设计的核心原则与实践能力。从基础类设计到继承体系构建,再到复杂业务逻辑的多态实现,过程中虽面临代码复杂度控制、测试覆盖等挑战,但通过合理的类职责划分、设计模式应用及持续重构,最终实现了可扩展、易维护的系统架构。未来需进一步将设计原则融入日常编码习惯,提升代码的健壮性与可复用性,为大型软件项目开发奠定坚实基础。
浙公网安备 33010602011771号