PTA题目集8-9总结:航空货运管理系统的面向对象设计

前言
题目集8和9是围绕“航空货运管理系统”设计的编程作业,主要是学习面向对象编程的知识点,比如怎么设计类、用继承和多态处理不同类型的客户和货物,还有怎么实现运费计算和载重检查。题目集8是基础版,搭建了系统的基本框架;题目集9在8的基础上增加了客户类型、货物类型和支付方式,难度稍微高了一点。
知识点总结

类的设计:需要设计Customer(客户)、Cargo(货物)、Flight(航班)和Order(订单)这些类,每个类只负责自己的功能,比如Cargo算运费,Flight管载重。
继承和多态:题目集9要求客户分个人和集团,货物分普通、加急和危险,通过继承让它们有不同的折扣或费率。
业务逻辑:计算计费重量(实际重量和体积重量取大的那个)、按重量分段算费率、处理折扣和支付方式。
异常处理:检查航班有没有超载,输入的数据要合法(比如重量不能是负数)。

题量和难度

题目集8:1道题,难度比较基础,主要是把类设计好,写清楚每个类的功能,比如Cargo算运费,Order汇总订单信息。
题目集9:1道题,难度中等,增加了客户类型(个人/集团)、货物类型(普通/加急/危险)和支付方式(微信/支付宝/现金),需要改代码支持这些新功能。
总计:两道题,题目集9是题目集8的升级版,逐步增加新需求,学着怎么扩展代码。

设计与分析
题目集8:基础版本设计
类结构分析
类图(简单描述):

Customer:存客户信息(编号、名字、电话、地址),有getName()和getPhone()方法。
Cargo:存货物信息(编号、名字、宽/长/高、重量),有getChargeableWeight()算计费重量,getRate()算费率,getFreightCost()算运费。
Flight:存航班信息(航班号、起飞/到达机场、日期、最大载重),有getFlightNumber()和getMaxLoadCapacity()方法。
Order:把Customer、Flight和List组合起来,算总重量(getTotalWeight())、总运费(getTotalCost())和检查超载(isOverloaded())。

代码复杂度(参考SourceMonitor的分析思路):

Cargo.getRate()因为用了好几个if-else(按重量分段),代码有点复杂,循环复杂度大概是5。
Order和Flight的代码比较简单,每个方法都只干一件事。
整个程序大概200行代码,方法复杂度平均在3左右,还算好维护。

核心代码:

// Cargo类:算计费重量
public double getChargeableWeight() {
    double volumeWeight = (width * length * height) / 6000.0;
    return Math.max(weight, volumeWeight);
}
// Cargo类:算费率
public double getRate() {
    double chargeableWeight = getChargeableWeight();
    if (chargeableWeight < 20) return 35.0;
    else if (chargeableWeight < 50) return 30.0;
    else if (chargeableWeight < 100) return 25.0;
    else return 15.0;
}
// Order类:算总运费
public double getTotalCost() {
    double total = 0;
    for (Cargo cargo : cargos) {
        total += cargo.getFreightCost();
    }
    return total;
}

分析:

Cargo把计费重量和运费计算放一起,逻辑清楚,但费率是用固定的if-else写的,如果要加新货物类型,得改代码,不太灵活。
Order把所有货物的运费加起来,代码简单,容易懂。
Flight只管航班信息和载重,功能单一,做得不错。

题目集9:扩展版本设计
类结构变化
类图(简单描述):

Customer:加了type(个人/集团),有getDiscount()方法,个人0.9折,集团0.8折。
Cargo:加了type(普通/加急/危险),getRate()根据货物类型和重量返回不同费率。
Flight:没变,跟题目集8一样。
Order:加了paymentMethod(微信/支付宝/现金),getTotalCost()要算折扣,printOrderDetails()显示支付方式的中文。

代码复杂度:

Cargo.getRate()因为要判断货物类型和重量,分支更多,复杂度大概到7。
Order.getTotalCost()加了折扣计算,稍微复杂一点,但还算清楚。
程序行数增加到250行左右,方法复杂度平均4左右,需要注意分支太多的问题。

核心代码:
// Customer类:算折扣
public double getDiscount() {
    return "Individual".equals(type) ? 0.9 : 0.8;
}
// Cargo类:算费率
public double getRate() {
    double chargeableWeight = getChargeableWeight();
    if ("Dangerous".equalsIgnoreCase(type)) {
        if (chargeableWeight < 20) return 80.0;
        else if (chargeableWeight < 50) return 50.0;
        else if (chargeableWeight < 100) return 30.0;
        else return 20.0;
    } else if ("Expedite".equalsIgnoreCase(type)) {
        if (chargeableWeight < 20) return 60.0;
        else if (chargeableWeight < 50) return 50.0;
        else if (chargeableWeight < 100) return 40.0;
        else return 30.0;
    } else {
        if (chargeableWeight < 20) return 35.0;
        else if (chargeableWeight < 50) return 30.0;
        else if (chargeableWeight < 100) return 25.0;
        else return 15.0;
    }
}
// Order类:算总运费(带折扣)
public double getTotalCost() {
    double total = 0;
    for (Cargo cargo : cargos) {
        total += cargo.getFreightCost();
    }
    return total * customer.getDiscount();
}

分析:

Customer用type来决定折扣,简单但不够灵活,如果加新客户类型还得改代码。
Cargo.getRate()用字符串type判断货物类型,代码重复多(比如普通货物的费率跟题目集8一样),而且分支太多,容易出错。
Order增加了支付方式的中文显示(比如Wechat变“微信”),但逻辑是硬编码的,扩展性差。

踩坑心得

  1. 货物类型代码重复
    问题:在题目集9的Cargo.getRate()里,普通货物的费率逻辑跟题目集8一模一样,复制粘贴了代码,显得很冗余。而且用字符串type判断类型,分支太多,改起来麻烦。解决办法:可以把Cargo改成抽象类,普通、加急、危险货物各写一个子类,每个子类自己算费率,这样代码就不重复了。测试数据:


货物类型
重量(kg)
体积(cm³)
计费重量
预期费率



Dangerous
100
600000
100
20.0


Expedite
19
100000
16.67
60.0


Normal
50
300000
50
25.0

心得:测试的时候发现危险货物重量100kg的费率错了(得了15.0,应该是20.0),因为没测够边界情况,以后要多写些测试用例。
2. 支付方式写死了
问题:Order里的支付方式用字符串判断(Wechat、ALiPay、Cash),如果要加新支付方式(比如银行卡),得改printOrderDetails()的代码,麻烦。解决办法:可以用一个单独的类来处理支付方式,比如定义一个Payment类,里面写好每种支付方式的中文名字。测试数据:



支付方式
预期输出



Wechat
微信支付金额


ALiPay
支付宝支付金额


Cash
现金支付金额

心得:写死逻辑让代码不好改,学到以后要尽量把这种逻辑单独抽出来。
3. 没检查输入数据
问题:代码没检查输入的重量或尺寸是不是合法(比如负数),如果输入错了,程序可能会出问题。解决办法:在Cargo的构造方法里加检查:

public Cargo(...) {
    if (width <= 0 || length <= 0 || height <= 0 || weight <= 0) {
        throw new IllegalArgumentException("尺寸或重量不合法");
    }
}

心得:一开始没考虑输入错误的情况,提交代码后才发现,输入检查很重要。
4. 航班载重可能有问题
问题:如果多个订单同时加货物,Flight的载重可能算错(虽然题目没说要多线程,但实际系统可能有这问题)。解决办法:可以用AtomicDouble来安全地更新载重:

private final AtomicDouble currentLoad = new AtomicDouble(0);
public void addLoad(double weight) {
    currentLoad.addAndGet(weight);
}

心得:设计代码时要考虑实际使用场景,提前防问题。
改进建议

  1. 用继承优化货物类型
    可以用Cargo抽象类,让NormalCargo、ExpediteCargo、DangerousCargo继承它,每种货物自己算费率,代码就不用重复了:
abstract class Cargo {
    protected double getBaseRate(double weight) {
        if (weight < 20) return 35.0;
        // 其他分段...
    }
}
class ExpediteCargo extends Cargo {
    public double getRate() {
        return getBaseRate(getChargeableWeight()) * 1.5; // 加急费率上浮50%
    }
}
  1. 支付方式单独处理
    把支付方式的逻辑抽出来,做成一个Payment类:
class Payment {
    private String type;
    public Payment(String type) { this.type = type; }
    public String getDisplayName() {
        if ("Wechat".equals(type)) return "微信支付金额";
        if ("ALiPay".equals(type)) return "支付宝支付金额";
        return "现金支付金额";
    }
}

这样加新支付方式就不用改Order的代码了。
3. 加强输入检查
在Cargo和Customer里加输入检查,比如重量、尺寸不能是负数,电话号码要符合格式。
4. 优化性能
如果货物很多,可以把Cargo.getChargeableWeight()的结果存起来,避免重复算。订单的货物列表用ArrayList够用了,但如果经常删加货物,可以考虑LinkedList。
总结
学到的东西

类的职责:学会把功能分清楚,比如Cargo只算运费,Flight只管载重,这样代码清楚好改。
继承和多态:题目集9用客户和货物类型让我理解了继承,学到怎么让子类替换父类。
测试重要性:写测试用例能发现代码问题,比如边界值(重量=100kg)测出来费率错误。
代码复用:题目集9复用了题目集8的代码,学到怎么在已有代码上加新功能。

需要改进的地方

代码简化:Cargo.getRate()分支太多,改起来麻烦,学了可以用继承或别的办法简化。
测试全面性:测试用例不够,没测负数输入或空订单,后面要写更多测试。
设计模式:还没用过工厂模式之类的高级方法,后面想多学点。

对课程的建议

继续迭代题目:像题目集8-9这样在一个系统上加需求,学起来很有连贯性,可以再加点新功能(比如多航班)。
多讲例子:希望老师多给点代码例子,讲讲怎么把if-else改成继承或别的办法。
测试指导:可以给点测试用例的模板,教我们怎么测边界和错误情况。

结语
题目集8-9让我从零开始建了一个航空货运系统,从简单的类设计到复杂的继承和多态,学到了怎么把代码写得清楚、好改。虽然遇到代码重复、输入没检查等问题,但通过测试和重构,系统变得更好用了。以后我会多注意测试用例和代码结构,争取写出更棒的程序!

posted @ 2025-05-23 15:00  紧张cool  阅读(39)  评论(0)    收藏  举报