Java航空货运管理系统题目集8~9总结

一、前言
1.题目集 8 和题目集 9 主要围绕 "航空货运管理系统" 展开,旨在考查面向对象设计原则的应用。题目集 8 作为基础版本,主要实现了货物计费重量计算、基础运费计算以及订单管理等核心功能;题目集 9 则在此基础上进行了扩展,增加了货物类型、用户类型及折扣率等因素,进一步考查了策略模式和依赖倒转原则的应用。在知识点方面,涵盖了类的封装、继承、多态、容器、接口设计、策略模式以及输入输出处理等核心内容。
2.两次题目集题目量不大,主要是对之前题目集代码的重构,以及对题目类的设计。
3.从难度上看,相较于之前题目集的算法考查,难度降低了很多。题目集 8 侧重于基础功能的实现和类的初步设计,题目集 9 则更注重设计原则的深入应用和系统的可扩展性。

二、设计与分析
题目集 8 设计与分析

类设计与职责划分
题目集 8 航空货运管理系统的核心类结构如下:
Customer 类:负责存储客户基本信息,包括客户编号、姓名、电话和地址。
Cargo 类:处理货物相关逻辑,包括计算体积重量、确定计费重量、计算费率和运费。
Flight 类:管理航班信息,包括航班号、起降机场、日期、最大载重量及当前载重情况。
Order 类:整合订单信息,包括订单基本信息、关联的航班和客户、货物列表以及总重量和费用计算。
Show 类:负责格式化输出订单信息和货物明细。
Main 类:主类,处理用户输入和流程控制。

设计类图

核心代码分析
Cargo类中,实现了关键的计费逻辑:

点击查看代码
public double calWeight() {
    return (length * width * height / 6000);
}

public double dealWeight() {
    double volumeWeight = calWeight();
    return Math.max(weight, volumeWeight);
}

public double getRate() {
    double dealWeight = dealWeight();
    if (dealWeight < 20) {
        return 35;
    } else if (dealWeight < 50) {
        return 30;
    } else if (dealWeight < 100) {
        return 25;
    } else {
        return 15;
    }
}

该代码中:calWeight()计算体积重量,dealWeight()确定计费重量,getRate()根据计费重量确定费率。

Order类中的构造方法整合了订单的所有要素,并计算总重量和总费用:

点击查看代码
public Order(String orderId, String orderDate, String senderAddress, String senderName, String senderPhone,
                 String receiverAddress, String receiverName, String receiverPhone, Flight flight, Customer customer,
                 Cargo[] cargos) {
        // 初始化订单基本信息
        this.orderId = orderId;
        this.orderDate = orderDate;
        this.senderAddress = senderAddress;
        this.senderName = senderName;
        this.senderPhone = senderPhone;
        this.receiverAddress = receiverAddress;
        this.receiverName = receiverName;
        this.receiverPhone = receiverPhone;
        this.flight = flight;
        this.customer = customer;
        this.cargos = cargos;
        this.payment = payment;
        // 计算总重量和总费用
        for (Cargo cargo : cargos) {
            totalWeight += cargo.dealWeight();
            totalCost += cargo.getCost();
        }
    }

该代码运用了合成复用原则,Order类通过组合 CustomerFlightCargo 对象来实现订单功能,而不是通过继承,使得类之间的耦合度降低,提高了代码的复用性。

设计原则应用分析
单一职责原则:每个类都有明确的职责,如Cargo类专注于货物相关计算,Flight类专注于航班信息管理。
合成复用原则Order类通过组合而非继承来使用其他类的功能,如组合 CustomerFlightCargo 对象。

SourceMontor分析



代码中包含 5 个类(Cargo、Customer、Flight、Order、Show ) 。文件总行数为 460 行,语句数 232 条,分支语句占比 2.2% ,方法调用语句 21 条,注释行占比为 0%。
类与方法复杂度分析
Cargo 类
方法数量较多,大部分方法复杂度为 1 ,如获取和设置属性的方法(getCargoId、setCargoId等 ),这些方法功能单一,符合单一职责原则。
getRate方法复杂度为 5 ,是最复杂的方法之一,在题目集 9 中该方法包含多种货物类型的费率计算逻辑,使用了大量条件判断,这导致了较高的复杂度。
dealWeight方法复杂度为 1 ,负责计算计费重量,逻辑相对简单。
Customer 类
方法复杂度大多为 1 ,主要是属性的获取和设置方法,功能明确,职责单一,符合面向对象设计原则。构造方法复杂度为 4 ,但整体来看方法逻辑并不复杂,由于参数处理等导致复杂度稍高。
Flight 类:
方法复杂度普遍为 1 ,主要处理航班相关信息的获取和设置,以及载重判断和载重增加等功能,每个方法专注于单一功能。构造方法有复杂度为 6 和 0 的情况,复杂度为 6 的构造方法涉及较多参数初始化操作。
Order 类
大部分方法复杂度为 1 ,但构造方法复杂度为 2 ,且语句数较多(15 条 ),深度为 3 ,调用数为 2 。在构造方法中,需要初始化订单的众多属性,并计算订单总重量和总费用,涉及对货物信息的遍历计算等操作,导致复杂度相对较高。
Show 类
showInfo1方法复杂度为 1 ,但语句数有 13 条,深度为 2 ,调用数为 13 。该方法主要负责格式化输出订单信息,由于输出内容较多,导致语句数较多。

题目集 9 设计与分析

类设计与扩展
题目集 9 航空货运管理系统在题目集 8 的基础上进行了以下关键扩展:
Customer 类:增加了customerType属性,用于区分个人用户和集团用户。
Cargo 类:增加了cargoType属性,用于区分普通货物、加急货物和危险货物,并修改了费率计算逻辑。
Order 类:增加了支付方式处理,并在计算总费用时应用用户折扣。
Main 类:调整了输入流程,增加了货物类型和用户类型的输入。

设计类图

策略模式的应用
题目集 9 中,Cargo 类的费率计算方法根据货物类型不同而变化:

点击查看代码
public double getRate() {
    double realWeight = realWeight();
    if ("Normal".equals(getCargoType())) {
        // 普通货物费率计算
        if (realWeight < 20) {
            return 35;
        } else if (realWeight < 50) {
            return 30;
        } else if (realWeight < 100) {
            return 25;
        } else {
            return 15;
        }
    } else if ("Expedite".equals(getCargoType())) {
        // 加急货物费率计算
        if (realWeight < 20) {
            return 60;
        } else if (realWeight < 50) {
            return 50;
        } else if (realWeight < 100) {
            return 40;
        } else {
            return 30;
        }
    } else if ("Dangerous".equals(getCargoType())) {
        // 危险货物费率计算
        if (realWeight < 20) {
            return 80;
        } else if (realWeight < 50) {
            return 50;
        } else if (realWeight < 100) {
            return 30;
        } else {
            return 20;
        }
    }
    return 0.0;
}

该代码通过传入不同的货物类型来调用相应的计算逻辑,体现了一定的策略思想

依赖倒转原则的体现
题目集 9 中,Cargo 类getCost1()方法根据用户类型计算折扣后的运费:

点击查看代码
public double getCost1() {
    if (customer == null) {
        return 0.0;
    }
    if("Individual".equals(customer.getCustomerType())){
        return realWeight() * getRate()*0.9;
    } else if("Corporate".equals(customer.getCustomerType())){
        return realWeight() * getRate()*0.8;
    }
    return 0.0;
}

这里 Cargo 类依赖于 Customer 类的抽象(用户类型),而不是具体的实现细节,体现了依赖倒转原则 —— 依赖于抽象而非具体。这样设计使得当需要添加新的用户类型时,只需修改 Customer 类的定义,而无需修改 Cargo 类的代码。

设计原则应用分析
单一职责原则:在题目集 9 中,各个类的职责更加明确,如 Customer 类专注于用户信息和类型,Cargo 类专注于货物信息和计费逻辑。
依赖倒转原则:Cargo 类依赖于 Customer 类的抽象(用户类型),而不是具体的用户实现。

SourceMontor分析



代码包含 5 个类,代码总行数 531 行,语句数 314 条,分支语句占比 7.6% ,方法调用语句 66 条,注释行占比为 0% 。平均每个类有 16.2 个方法,平均每个方法有 2.38 条语句 ,平均复杂度为 1.30 ,平均块深度为 1.93 。
类与方法分析
Cargo 类
复杂方法突出:Cargo.getRate方法复杂度高达 16 ,是最复杂的方法,语句数 29 条,最大深度 4 ,方法调用数 7 。在题目集 9 中,该方法负责根据不同货物类型(普通、加急、危险 )和重量段计算费率,使用了大量条件判断,逻辑复杂。可通过策略模式将不同货物类型的费率计算逻辑拆分到独立的策略类中,降低方法复杂度,提高可维护性和可扩展性。
多数方法较简单:大部分获取和设置属性的方法(如getCargoId、setCargoId等 )复杂度为 1 ,功能单一,符合单一职责原则。但Cargo.getCost1方法复杂度为 4 ,该方法在计算货物费用时,需要结合用户类型(个人用户 9 折、集团用户 8 折 )进行折扣计算,涉及额外逻辑判断,导致复杂度上升。
Customer 类
方法复杂度低:该类方法复杂度大多为 1 ,主要是属性的获取和设置方法,如getAddress、setAddress等 ,功能明确,职责单一,较好地遵循了单一职责原则。构造方法复杂度为 5 ,因为参数处理或属性初始化逻辑稍多,但整体逻辑不算复杂。
Flight 类
方法功能单一:方法复杂度普遍为 1 ,主要负责航班信息(如航班号、起降机场、日期、载重等 )的获取和设置,以及载重判断(judgeLoad )和载重增加(addLoad )等功能,每个方法专注于单一功能,符合面向对象设计原则。构造方法复杂度有 6 和 0 的情况,复杂度为 6 的构造方法涉及较多参数初始化操作。
Order 类
构造方法复杂:Order.Order构造方法复杂度为 15 ,语句数 25 条,最大深度 3 ,调用数 2 。该方法需初始化订单众多属性,包括订单基本信息、关联的航班、客户、货物列表等,还需计算订单总重量和总费用,涉及对货物信息的遍历计算等操作,导致复杂度较高。
部分方法较简单:多数属性的获取和设置方法复杂度为 1 ,功能单一。但Order.getRealPayment方法复杂度为 4 ,该方法将英文支付方式转换为中文,可能因条件判断较多导致复杂度上升。
Main 类
main方法较复杂:Main.main方法复杂度为 3 ,语句数 45 条,最大深度 4 ,调用数 34 。作为程序入口,该方法负责处理用户输入,包括客户、货物、航班、订单等各类信息的读取,还需创建相关对象并进行业务逻辑处理,调用众多其他类的方法,因此复杂度较高。

三、踩坑心得
题目集 8 踩坑记录
1.输入处理中的换行符问题
在处理多个输入时,特别是在nextInt()之后读取字符串时,容易遗留换行符导致输入错位。例如:

点击查看代码
int cargoCount = sc.nextInt();
sc.nextLine(); // 必须添加这行来消耗掉换行符

一开始没有注意到这个问题,导致后续的字符串输入读取到了换行符,而非实际数据。通过添加sc.nextLine()来消耗多余的换行符解决了该问题。

2.浮点数精度与输出格式问题
在计算体积重量和运费时,浮点数的精度问题导致输出结果与样例不符。例如,当计算结果为整数时,输出可能显示为125.0而非125,但题目要求保留 1 位小数,因此必须使用格式化输出:

点击查看代码
System.out.printf("订单总重量(kg):%.1f\n", order.getTotalWeight());

通过使用String.format或System.out.printf并指定%.1f格式,确保了输出结果的准确性。

3.类职责划分的初始混乱
一开始将部分订单处理逻辑放在了 Main 类中,导致 Main 类职责过重。通过重构,将订单信息的处理、报表生成等功能移到 Order 类和 Show 类中,使每个类的职责更加单一。

题目集 9 踩坑记录
1.货物类型与用户类型的映射问题
在 Cargo 类中,getCost1()方法需要访问 Customer 类的用户类型来计算折扣,但一开始没有正确建立 Cargo 与 Customer 之间的关联,导致customer对象为null。通过在 Cargo 类中添加setCustomer()方法并在订单创建时建立关联解决了该问题:

点击查看代码
cargos[i].setCustomer(customer);

2.策略模式实现的不规范
题目集 9 中虽然需要根据货物类型采用不同的费率策略,但最初的实现使用了大量的条件判断,而非规范的策略模式。这导致代码的可扩展性较差,当需要添加新的货物类型时,必须修改getRate()方法的条件判断逻辑,违背了开闭原则。

3.账单总费用是折扣后的而单个货物费用不是
题目集 9 的账单输出支付金额为折扣后的,而单个货物的不是,一开始忽视了这点,都调用了折扣后的费用,导致结果输出错误。总金额需调用折扣后的费用,而单个货物需要调用未打折的费用。

四、改进建议
代码结构改进
1.引入策略模式规范实现
题目集 9 中的费率计算可以通过规范的策略模式来实现,定义策略接口:

点击查看代码
public interface RateStrategy {
    double calculateRate(double weight);
}

然后为每种货物类型实现具体策略:

点击查看代码
public class NormalCargoStrategy implements RateStrategy {
    @Override
    public double calculateRate(double weight) {
        if (weight < 20) return 35;
        if (weight < 50) return 30;
        if (weight < 100) return 25;
        return 15;
    }
}

在 Cargo 类中使用策略接口:

点击查看代码
private RateStrategy rateStrategy;

public void setCargoType(String cargoType) {
    this.cargoType = cargoType;
    // 根据货物类型选择策略
    switch (cargoType) {
        case "Normal":
            rateStrategy = new NormalCargoStrategy();
            break;
        case "Expedite":
            rateStrategy = new ExpediteCargoStrategy();
            break;
        case "Dangerous":
            rateStrategy = new DangerousCargoStrategy();
            break;
    }
}

public double getRate() {
    return rateStrategy.calculateRate(realWeight());
}

这种实现方式遵循了开闭原则,当需要添加新的货物类型时,只需添加新的策略类,无需修改现有代码。

2.使用枚举类优化类型判断
将货物类型和用户类型定义为枚举类,避免使用字符串比较:

点击查看代码
public enum CargoType {
    NORMAL, EXPEDITE, DANGEROUS
}

public enum UserType {
    INDIVIDUAL, CORPORATE
}

在类中使用枚举类型:

点击查看代码
private CargoType cargoType;
private UserType userType;

枚举类的使用使代码更加类型安全,避免了字符串拼写错误,并提高了代码的可读性。

3.优化 Order 类的构造方法
Order 类的构造方法参数过多,可考虑使用建造者模式来简化对象创建过程,提高代码的可读性和可维护性。

功能扩展建议
1.添加附加费用计算
实现燃油附加费、安检费等附加费用的计算,根据不同的货物类型或航班距离应用不同的附加费用。
2.引入运输距离因素
在费率计算中考虑运输距离,不同的航线距离设置不同的基础费率,使计费逻辑更加真实。
3.订单状态管理
添加订单状态(待支付、已支付、运输中、已送达等)的管理功能,跟踪订单的整个生命周期。

代码规范改进
1.增加注释
在关键方法和复杂逻辑处添加详细注释,说明方法的功能、参数和返回值,提高代码的可读性。
2.统一命名规范
检查变量名、方法名是否符合驼峰命名法,确保命名一致且具有描述性。
3.减少代码重复
检查代码中是否存在重复的逻辑或结构,提取公共部分形成工具方法或基类。例如,多个类中的输入处理逻辑可以封装到一个单独的工具类中。

五、总结
通过对题目集 8 和题目集 9 代码的分析,可以看出代码在实现基本功能的同时,存在一些需要改进的地方,如部分方法复杂度高、设计模式应用不足、缺乏注释等。通过遵循面向对象设计原则,重构复杂方法,引入适当的设计模式,以及优化代码结构和规范,可以提高代码的质量、可维护性和可扩展性。这两次题目集的实践也让我深刻理解了面向对象设计原则在实际项目中的重要性,以及如何运用这些原则来设计和优化代码。在未来的学习和实践中,我将继续努力,不断提升自己的代码设计和实现能力。
教师在布置编程作业之前,可以在课上举几个类似的例子,便于我们了解编程题目,更容易上手。

posted @ 2025-05-23 20:02  LlinY  阅读(29)  评论(0)    收藏  举报