面向对象程序设计课程题目集 8-9 总结性 Blog
——————————24201321————黄永铎
一、前言
好烦好烦好烦。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
题目集 8 和题目集 9 围绕 “继承与多态”“容器类操作”“复杂系统设计” 等核心知识点展开,涵盖了类的抽象、接口的应用、数据结构的选择以及系统业务逻辑的实现等多个方面。题目集 8 包含 3 道题目,主要涉及点线面的继承重构、电梯调度系统代码分析以及航空货运管理系统的初步设计;题目集 9 在此基础上新增 3 道题目,重点考察魔方类的多态实现、几何对象容器的增删改查以及航空货运系统的扩展设计。从难度上看,题目集 8 以基础类设计和代码分析为主,难度适中;题目集 9 进一步深化系统复杂度,尤其在航空货运系统中引入客户类型、货物类型和支付方式等多维度业务逻辑,对类的封装性、代码的扩展性提出了更高要求。
二、设计与分析
(一)题目集 8 - 航空货运管理系统(基础版)
代码

点击查看代码
import java.text.DecimalFormat;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;

class Customer {
    private String id;
    private String name;
    private String phone;
    private String address;
    public Customer(String id, String name, String phone, String address) {
        this.id = id;
        this.name = name;
        this.phone = phone;
        this.address = address;
    }
    public String getDisplayInfo() {
        return new StringBuffer().append(name).append("(").append(phone).append(")").toString();
    }
}

class Cargo {
    private String id;
    private String name;
    private double width;
    private double length;
    private double height;
    private double weight;
    private double chargeWeight;
    private double rate;
    private double fee;
    public Cargo(String id, String name, double width, double length, double height, double weight) {
        this.id = id;
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.weight = weight;
        calculateChargeWeight();
        calculateRate();
        calculateFee();
    }
    private void calculateChargeWeight() {
        double volume = width * length * height;
        double volumeWeight = volume / 6000;
        chargeWeight = Math.max(weight, volumeWeight);
    }
    private void calculateRate() {
        if (chargeWeight < 20) rate = 35;
        else if (chargeWeight < 50) rate = 30;
        else if (chargeWeight < 100) rate = 25;
        else rate = 15;
    }
    private void calculateFee() {
        fee = chargeWeight * rate;
    }
    public double getChargeWeight() { return chargeWeight; }
    public double getRate() { return rate; }
    public double getFee() { return fee; }
    public String getDisplayInfo(int sequence) {
        DecimalFormat df = new DecimalFormat("#0.0");
        return new StringBuffer()
            .append(sequence).append("\t")
            .append(name).append("\t")
            .append(df.format(chargeWeight)).append("\t")
            .append(df.format(rate)).append("\t")
            .append(df.format(fee))
            .toString();
    }
}

class Flight {
    private String id;
    private String departure;
    private String arrival;
    private String date;
    private double maxLoad;
    private double currentLoad;
    public Flight(String id, String departure, String arrival, String date, double maxLoad) {
        this.id = id;
        this.departure = departure;
        this.arrival = arrival;
        this.date = date;
        this.maxLoad = maxLoad;
        this.currentLoad = 0;
    }
    public boolean checkLoad(double weight) {
        return (currentLoad + weight) <= maxLoad;
    }
    public void addLoad(double weight) {
        currentLoad += weight;
    }
    public String getId() { return id; }
}

class Order {
    private String id;
    private String date;
    private String senderName;
    private String senderPhone;
    private String senderAddress;
    private String receiverName;
    private String receiverPhone;
    private String receiverAddress;
    private double totalWeight;
    private double totalFee;
    private DecimalFormat df = new DecimalFormat("#0.0");
    public Order(String id, String date, String senderAddress, String senderName, 
                String senderPhone, String receiverAddress, String receiverName, String receiverPhone) {
        this.id = id;
        this.date = date;
        this.senderAddress = senderAddress;
        this.senderName = senderName;
        this.senderPhone = senderPhone;
        this.receiverAddress = receiverAddress;
        this.receiverName = receiverName;
        this.receiverPhone = receiverPhone;
    }
    public void addCargoFee(double weight, double fee) {
        totalWeight += weight;
        totalFee += fee;
    }
    public String getDisplayInfo(Customer customer, Flight flight) {
        StringBuffer sb = new StringBuffer();
        sb.append("客户:").append(customer.getDisplayInfo()).append("订单信息如下:\n")
          .append("-----------------------------------------\n")
          .append("航班号:").append(flight.getId()).append("\n")
          .append("订单号:").append(id).append("\n")
          .append("订单日期:").append(date).append("\n")
          .append("发件人姓名:").append(senderName).append("\n")
          .append("发件人电话:").append(senderPhone).append("\n")
          .append("发件人地址:").append(senderAddress).append("\n")
          .append("收件人姓名:").append(receiverName).append("\n")
          .append("收件人电话:").append(receiverPhone).append("\n")
          .append("收件人地址:").append(receiverAddress).append("\n")
          .append("订单总重量(kg):").append(df.format(totalWeight)).append("\n")
          .append("微信支付金额:").append(df.format(totalFee));
        return sb.toString();
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Customer customer = new Customer(sc.nextLine(), sc.nextLine(), sc.nextLine(), sc.nextLine());
        int cargoCount = Integer.parseInt(sc.nextLine());
        Map<Integer, Cargo> cargos = new LinkedHashMap<>();
        for (int i = 0; i < cargoCount; i++) {
            String id = sc.nextLine();
            String name = sc.nextLine();
            double width = Double.parseDouble(sc.nextLine());
            double length = Double.parseDouble(sc.nextLine());
            double height = Double.parseDouble(sc.nextLine());
            double weight = Double.parseDouble(sc.nextLine());
            cargos.put(i+1, new Cargo(id, name, width, length, height, weight));
        }
        Flight flight = new Flight(sc.nextLine(), sc.nextLine(), sc.nextLine(), sc.nextLine(), Double.parseDouble(sc.nextLine()));
        Order order = new Order(
            sc.nextLine(), sc.nextLine(), 
            sc.nextLine(), sc.nextLine(), sc.nextLine(),
            sc.nextLine(), sc.nextLine(), sc.nextLine()
        );
        boolean overload = false;
        for (Map.Entry<Integer, Cargo> entry : cargos.entrySet()) {
            Cargo cargo = entry.getValue();
            double weight = cargo.getChargeWeight();
            if (!flight.checkLoad(weight)) {
                overload = true;
                break;
            }
            flight.addLoad(weight);
            order.addCargoFee(weight, cargo.getFee());
        }
        if (overload) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.", flight.getId());
        } else {
            System.out.println(order.getDisplayInfo(customer, flight));
            System.out.println("\n货物明细如下:\n-----------------------------------------");
            System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
            for (Map.Entry<Integer, Cargo> entry : cargos.entrySet()) {
                System.out.println(entry.getValue().getDisplayInfo(entry.getKey()));
            }
        }
    }
}
1. 类设计与 UML 类图分析: ![](https://img2024.cnblogs.com/blog/3638209/202505/3638209-20250524140931059-1544372873.png)

参数分析:



核心类与方法分析

  1. Main 类:主逻辑入口
    • 复杂度最高方法:main()
    o 复杂度值:7(中等复杂度),包含 30 行代码,39 次方法调用,逻辑集中于输入处理和对象初始化。
    o 问题:
    o 职责过重:承担输入解析、对象创建、业务逻辑调度等多项职责,违反单一职责原则;
    o 代码冗长:143 行的main方法包含多层嵌套(最大块深度 4),可读性差;
    o 耦合度高:直接操作Cargo/Flight/Order类的属性,未通过接口或服务层解耦。
    o 代码片段示例:
点击查看代码
// main方法中直接处理输入与业务逻辑
Customer customer = new Customer(sc.nextLine(), sc.nextLine(), sc.nextLine(), sc.nextLine());
int cargoCount = Integer.parseInt(sc.nextLine());
for (int i = 0; i < cargoCount; i++) {
    Cargo cargo = new Cargo(...);
    orders.add(cargo);
}
Flight flight = new Flight(...);
if (!flight.checkLoad(totalWeight)) { /* 直接调用Flight逻辑 */ }


2. Cargo 类:货物计算逻辑
•	关键方法:
o	calculateRate():复杂度 5,实现阶梯式费率计算,逻辑如下:
java
if (chargeWeight < 20) rate = 35;
else if (chargeWeight < 50) rate = 30;
else if (chargeWeight < 100) rate = 25;
else rate = 15; // 硬编码费率,难以扩展

问题:费率规则硬编码,新增费率等级需修改代码,违反开闭原则。 o calculateChargeWeight():复杂度 1,逻辑简单(体积重量与实际重量取最大值),但未校验尺寸为负数的情况。 • 命名问题: o 构造方法名拼写错误:Carao()应为Cargo(),可能导致类实例化异常。 3. Flight 类:航班载重量管理 • 构造方法复杂度:Flight()复杂度 6,包含 6 行代码,可能涉及参数初始化或校验:
点击查看代码
public Flight(String id, String dep, String arr, String date, double maxLoad) {
    this.id = id;
    this.departure = dep;
    this.arrival = arr;
    this.date = date;
    this.maxLoad = maxLoad; // 简单赋值,复杂度来源可能为参数校验
    if (maxLoad <= 0) throw new IllegalArgumentException("Max load must be positive"); // 假设存在校验逻辑
}

优点:通过构造方法校验参数合法性,提升健壮性。 4. Order 类:订单信息展示 • getDisplayInfo()方法:复杂度 3,包含字符串拼接和格式化输出,例如:
点击查看代码
public String getDisplayInfo() {
    return "客户:" + customer.getName() + "\n" +
           "订单总重量:" + totalWeight + "kg\n" +
           "货物明细:" + cargoList.stream().map(Cargo::getDisplayInfo).collect(Collectors.joining("\n"));
}

问题:输出逻辑与业务逻辑耦合,可提取独立的OrderFormatter类。
代码质量指标分析

  1. 可维护性问题
    • 注释覆盖率:7.1%(严重不足),仅Main类有少量注释,Cargo类的费率计算、Flight类的载重量校验等核心逻辑完全无注释,导致代码理解成本高。
    o 示例:
点击查看代码
// 无注释,无法快速理解逻辑
double volumeWeight = width * length * height / 6000;
chargeWeight = Math.max(weight, volumeWeight);

建议:

点击查看代码
// 计算体积重量(规则:长×宽×高÷6000,单位:kg)
double volumeWeight = width * length * height / 6000;
// 计费重量取实际重量与体积重量的较大值(物流行业抛重规则)
chargeWeight = Math.max(weight, volumeWeight);

• 类复杂度分布:
o Main类复杂度最高(7),其次是Cargo类(5),Customer和Order类复杂度较低,设计相对简单。
2. 代码规范与命名
• 拼写错误:
o Carao类名错误(应为Cargo),导致类定义和实例化错误;
o getDisplaylnfo()方法名拼写错误(lnfo应为Info),影响代码可读性。
• 方法命名不规范:
o calculate ChargeWeight()(含空格)应为calculateChargeWeight(),违反驼峰命名法。
3. 逻辑风险点
• 输入校验缺失:
o 未校验货物尺寸(宽、长、高)和重量是否为负数,可能导致chargeWeight计算异常;
o 未校验航班最大载重量maxLoad是否为正数,可能引发Flight对象状态无效。
• 异常处理缺失:
o 输入非数字字符时(如用户输入字母代替重量),程序会抛出NumberFormatException,但未使用try-catch捕获,导致程序崩溃。
性能与扩展性分析

  1. 性能关注点
    • 循环效率:
    o 在main方法中使用普通for循环遍历货物,虽无性能问题,但可改用增强型for循环提升可读性:
    • 数据结构选择:
    o 使用ArrayList存储货物列表,适用于频繁的添加操作,符合需求;
    o 无大规模数据处理,性能瓶颈不明显。
  2. 扩展性缺陷
    • 费率规则硬编码:
    货物费率计算逻辑紧耦合在Cargo类中,新增货物类型(如 “易碎品” 需额外费率)时需修改calculateRate()方法,违反开闭原则。
    改进方案:
点击查看代码
// 定义策略接口
public interface RateCalculator {
    double calculateRate(double chargeWeight);
}
// 具体策略实现
public class NormalRateCalculator implements RateCalculator { ... }
// Cargo类注入策略
private RateCalculator rateCalculator;
public double getRate() { return rateCalculator.calculateRate(chargeWeight); }

• 订单输出格式固化: 订单信息输出格式直接写死在Order.getDisplayInfo()中,若需支持 JSON/XML 格式输出,需修改代码。建议引入OutputFormatStrategy接口解耦输出逻辑。

核心类职责:
Customer 类:封装客户基本信息,提供信息展示方法getDisplayInfo(),通过构造方法初始化客户编号、姓名、电话和地址。
Cargo 类:处理货物计费逻辑,通过calculateChargeWeight()计算体积重量与实际重量的最大值作为计费重量,根据计费重量动态确定费率calculateRate(),最终计算运费calculateFee()。
Flight 类:管理航班载重量,通过checkLoad()方法校验订单总重量是否超限,addLoad()方法更新当前载重量。
Order 类:协调客户、航班和货物,通过addCargoFee()累加货物费用,getDisplayInfo()整合订单信息并格式化输出。
2. 关键代码逻辑分析
货物计费逻辑:

点击查看代码
private void calculateChargeWeight() {
    double volume = width * length * height;
    double volumeWeight = volume / 6000;
    chargeWeight = Math.max(weight, volumeWeight); // 取实际重量与体积重量的较大值
}
private void calculateRate() {
    if (chargeWeight < 20) rate = 35;
    else if (chargeWeight < 50) rate = 30;
    else if (chargeWeight < 100) rate = 25;
    else rate = 15; // 阶梯式费率设计
}

问题:费率规则硬编码在Cargo类中,若业务规则变更需修改类内部代码,违反开闭原则。
改进方向:将费率规则抽象为策略接口,通过策略模式实现动态切换。
订单与航班交互:

点击查看代码
for (Map.Entry<Integer, Cargo> entry : cargos.entrySet()) {
    Cargo cargo = entry.getValue();
    double weight = cargo.getChargeWeight();
    if (!flight.checkLoad(weight)) { // 逐货物校验载重量
        overload = true;
        break;
    }
    flight.addLoad(weight);
    order.addCargoFee(weight, cargo.getFee());
}

优点:逐货物校验确保航班载重量精确控制,避免整体超重导致的订单失败。
缺点:循环校验逻辑与订单处理耦合在Main类中,可将校验逻辑封装到Flight或Order类中,提升内聚性。
(二)题目集 9 - 航空货运管理系统(扩展版)
代码:

点击查看代码
import java.util.HashMap;
import java.util.Scanner;

// 客户类
class Customer {
    private final String type;        // 客户类型
    private final String name;        // 姓名
    private final String phone;       // 电话
    private final String address;     // 地址

    public Customer(String type, String name, String phone, String address) {
        this.type = type;
        this.name = name;
        this.phone = phone;
        this.address = address;
    }

    public String getType() { return type; }
    public String getName() { return name; }
    public String getPhone() { return phone; }
}

// 货物类
class Cargo {
    private final int id;              // 明细编号
    private final String name;         // 货物名称
    private final double width;        // 宽(cm)
    private final double length;       // 长(cm)
    private final double height;       // 高(cm)
    private final double actualWeight; // 实际重量(kg)
    private final String type;         // 货物类型
    private final double volumeWeight; // 体积重量(kg)
    private final double chargeWeight; // 计费重量(kg)
    private final double rate;         // 费率(CNY/kg)
    private final double freight;      // 应交运费(未折扣)

    public Cargo(int id, String name, double width, double length, double height,
                 double actualWeight, String type) {
        this.id = id;
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.actualWeight = actualWeight;
        this.type = type;
        
        // 计算体积重量(保留1位小数)
        volumeWeight = Math.round((width * length * height / 6000) * 10) / 10.0;
        chargeWeight = Math.max(actualWeight, volumeWeight);
        rate = determineRate(chargeWeight, type);
        freight = Math.round(chargeWeight * rate * 10) / 10.0;
    }

    private double determineRate(double weight, String type) {
        switch (type) {
            case "Normal":    // 普通货物
                if (weight < 20) return 35;
                else if (weight < 50) return 30;
                else if (weight < 100) return 25;
                else return 15;
            case "Dangerous": // 危险货物
                if (weight < 20) return 80;
                else if (weight < 50) return 50;
                else if (weight < 100) return 30;
                else return 20;
            case "Expedite":  // 加急货物
                if (weight < 20) return 60;
                else if (weight < 50) return 50;
                else if (weight < 100) return 40;
                else return 30;
            default: return 0;
        }
    }

    public int getId() { return id; }
    public double getChargeWeight() { return chargeWeight; }
    public double getRate() { return rate; }
    public double getFreight() { return freight; }
    public String getName() { return name; }
}

// 航班类
class Flight {
    private final String flightNumber;     // 航班号
    private final String departureAirport; // 起飞机场
    private final String arrivalAirport;   // 降落机场
    private final String date;             // 日期
    private final double maxWeight;        // 最大载重量(kg)

    public Flight(String flightNumber, String departureAirport,
                  String arrivalAirport, String date, double maxWeight) {
        this.flightNumber = flightNumber;
        this.departureAirport = departureAirport;
        this.arrivalAirport = arrivalAirport;
        this.date = date;
        this.maxWeight = maxWeight;
    }

    public String getFlightNumber() { return flightNumber; }
    public double getMaxWeight() { return maxWeight; }
}

// 订单类
class Order {
    private final String orderNumber;      // 订单号
    private final String orderDate;        // 订单日期
    private final String senderAddress;    // 发件人地址
    private final String senderName;       // 发件人姓名
    private final String senderPhone;      // 发件人电话
    private final String receiverAddress;  // 收件人地址
    private final String receiverName;     // 收件人姓名
    private final String receiverPhone;    // 收件人电话
    private final String paymentMethod;    // 支付方式
    private final Customer customer;       // 客户
    private final Flight flight;           // 航班
    private final HashMap<Integer, Cargo> cargoMap = new HashMap<>(); // 货物清单

    public Order(String orderNumber, String orderDate, String senderAddress,
                 String senderName, String senderPhone, String receiverAddress,
                 String receiverName, String receiverPhone, String paymentMethod,
                 Customer customer, Flight flight) {
        this.orderNumber = orderNumber;
        this.orderDate = orderDate;
        this.senderAddress = senderAddress;
        this.senderName = senderName;
        this.senderPhone = senderPhone;
        this.receiverAddress = receiverAddress;
        this.receiverName = receiverName;
        this.receiverPhone = receiverPhone;
        this.paymentMethod = paymentMethod;
        this.customer = customer;
        this.flight = flight;
    }

    public void addCargo(Cargo cargo) { cargoMap.put(cargo.getId(), cargo); }
    public double getTotalWeight() {
        return cargoMap.values().stream().mapToDouble(Cargo::getChargeWeight).sum();
    }
    public double getTotalFreight() {
        return cargoMap.values().stream().mapToDouble(Cargo::getFreight).sum();
    }
    public double getDiscountRate() {
        return "Corporate".equals(customer.getType()) ? 0.8 : 0.9;
    }
    public double getPaymentAmount() {
        return Math.round(getTotalFreight() * getDiscountRate() * 10) / 10.0;
    }

    public String getOrderNumber() { return orderNumber; }
    public String getOrderDate() { return orderDate; }
    public String getSenderAddress() { return senderAddress; }
    public String getSenderName() { return senderName; }
    public String getSenderPhone() { return senderPhone; }
    public String getReceiverAddress() { return receiverAddress; }
    public String getReceiverName() { return receiverName; }
    public String getReceiverPhone() { return receiverPhone; }
    public String getPaymentMethodCN() {
        switch (paymentMethod) {
            case "Wechat": return "微信";
            case "ALiPay": return "支付宝";
            case "Cash": return "现金";
            default: return paymentMethod;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取客户信息
        String customerType = scanner.next();          // 客户类型
        String customerId = scanner.next();            // 客户编号
        String customerName = scanner.next();          // 姓名
        String customerPhone = scanner.next();         // 电话
        scanner.nextLine(); // 消耗前面的换行或空格
        String customerAddress = scanner.nextLine().trim(); // 客户地址

        // 读取货物信息
        String cargoType = scanner.next();             // 货物类型
        int cargoCount = scanner.nextInt();            // 货物数量

        HashMap<Integer, Cargo> cargoMap = new HashMap<>();
        
        for (int i = 0; i < cargoCount; i++) {
            int id = i + 1; 
            // 读取并忽略输入的货物编号
            scanner.nextInt(); 
            String name = scanner.next();             
            double width = scanner.nextDouble();       
            double length = scanner.nextDouble();      
            double height = scanner.nextDouble();      
            double actualWeight = scanner.nextDouble();
            Cargo cargo = new Cargo(id, name, width, length, height, actualWeight, cargoType);
            cargoMap.put(id, cargo);
        }

        // 读取航班信息
        String flightNumber = scanner.next();          // 航班号
        String depAirport = scanner.next();            // 起飞机场
        String arrAirport = scanner.next();            // 降落机场
        String flightDate = scanner.next();            // 航班日期
        double maxWeight = scanner.nextDouble();       // 最大载重量

        // 读取订单信息
        String orderNumber = scanner.next();           // 订单号
        String orderDate = scanner.next();             // 订单日期
        scanner.nextLine(); // 消耗订单日期后的换行
        String senderAddress = scanner.nextLine().trim(); // 发件人地址
        String senderName = scanner.next();            // 发件人姓名
        String senderPhone = scanner.next();           // 发件人电话
        scanner.nextLine(); // 消耗发件人电话后的换行
        String receiverAddress = scanner.nextLine().trim(); // 收件人地址
        String receiverName = scanner.next();          // 收件人姓名
        String receiverPhone = scanner.next();         // 收件人电话
        String paymentMethod = scanner.next();         // 支付方式

        // 创建对象
        Customer customer = new Customer(customerType, customerName, customerPhone, customerAddress);
        Flight flight = new Flight(flightNumber, depAirport, arrAirport, flightDate, maxWeight);
        Order order = new Order(orderNumber, orderDate, senderAddress, senderName, senderPhone,
                receiverAddress, receiverName, receiverPhone, paymentMethod, customer, flight);

        // 添加货物到订单
        cargoMap.values().forEach(order::addCargo);

        // 检查航班载重量
        double totalWeight = order.getTotalWeight();
        if (totalWeight > flight.getMaxWeight()) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.%n",
                    flight.getFlightNumber());
            scanner.close();
            return;
        }

        // 输出订单信息
        System.out.printf("客户:%s(%s)订单信息如下:%n", customer.getName(), customer.getPhone());
        System.out.println("-----------------------------------------");
        System.out.printf("航班号:%s%n", flight.getFlightNumber());
        System.out.printf("订单号:%s%n", order.getOrderNumber());
        System.out.printf("订单日期:%s%n", order.getOrderDate());
        System.out.printf("发件人姓名:%s%n", order.getSenderName());
        System.out.printf("发件人电话:%s%n", order.getSenderPhone());
        System.out.printf("发件人地址:%s%n", order.getSenderAddress());
        System.out.printf("收件人姓名:%s%n", order.getReceiverName());
        System.out.printf("收件人电话:%s%n", order.getReceiverPhone());
        System.out.printf("收件人地址:%s%n", order.getReceiverAddress());
        System.out.printf("订单总重量(kg):%.1f%n", totalWeight);
        System.out.printf("%s支付金额:%.1f%n%n", order.getPaymentMethodCN(), order.getPaymentAmount());

        // 输出货物明细
        System.out.println("货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
        cargoMap.values().stream()
                .sorted((c1, c2) -> Integer.compare(c1.getId(), c2.getId()))
                .forEach(c -> System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n", 
                        c.getId(), c.getName(), c.getChargeWeight(), c.getRate(), c.getFreight()));

        scanner.close();
    }
}
1. 类设计扩展与 UML 类图 ![](https://img2024.cnblogs.com/blog/3638209/202505/3638209-20250524142138608-404211744.png)

新增设计:
客户类型:通过继承Customer类创建IndividualCustomer(个人客户)和CorporateCustomer(企业客户),未来可扩展客户等级等属性。
货物类型:定义Cargo抽象类,派生出NormalCargo(普通货物)、ExpediteCargo(加急货物)、DangerousCargo(危险货物),通过多态实现不同类型货物的费率计算。
支付方式:在Order类中增加paymentMethod属性,通过枚举或策略模式处理不同支付方式的手续费计算。
参数分析:





核心类深度分析

  1. Cargo 类:货物计费逻辑
    复杂度最高方法:determineRate()
    复杂度值:14(高于阈值 10),包含 4 层分支嵌套,逻辑为阶梯式费率判断:
点击查看代码
if (chargeWeight < 20) {
    rate = 35;
} else if (chargeWeight < 50) {
    rate = 30;
} else if (chargeWeight < 100) {
    rate = 25;
} else {
    rate = 15;
}

问题:
硬编码费率规则,违反开闭原则;
分支过多导致可读性下降,维护成本高。
优化建议:
引入策略模式,将费率计算封装为独立策略类(如RateStrategy接口);
使用枚举类定义费率区间,减少分支嵌套。
其他方法:
calculateChargeWeight():复杂度 1,逻辑简单(体积重量与实际重量取最大值),但未校验尺寸 / 重量为负数的情况;
getChargeWeight()等 getter 方法:复杂度 1,符合规范。
2. Order 类:订单处理
复杂度最高方法:getPaymentMethodCN()
复杂度值:2,通过简单条件判断转换支付方式中文名称:

点击查看代码
public String getPaymentMethodCN() {
    if ("Wechat".equals(paymentMethod)) {
        return "微信";
    } else if ("ALiPay".equals(paymentMethod)) {
        return "支付宝";
    } else {
        return "现金";
    }
}

问题:支付方式扩展时需修改方法,可优化为枚举映射。 关键属性: totalWeight/totalFee:通过循环累加货物费用,逻辑分散在main函数中,建议封装到Order类内部。 3. Main 类:主逻辑入口 方法复杂度:main()方法复杂度 3,包含 63 行代码,负责输入处理、对象创建、逻辑调度。 问题: 输入校验逻辑分散(如客户类型、货物类型校验),未封装到独立方法; 与Cargo/Flight/Order类耦合度高,违反单一职责原则。 优化方向: 提取InputParser类处理输入校验; 将订单创建逻辑封装为OrderFactory类。 代码质量指标分析 1. 可维护性指标 注释覆盖率:12.9%(低于行业标准 20%),尤其缺乏对计费规则、载重量校验等核心逻辑的注释。 示例缺失注释的代码:
点击查看代码
// 原代码未注释,无法快速理解逻辑
double volumeWeight = volume / 6000;
chargeWeight = Math.max(weight, volumeWeight);

建议补充:
// 计算体积重量(规则:体积/6000,单位:kg)
double volumeWeight = volume / 6000;
// 计费重量取实际重量与体积重量的较大值(抛重规则)
chargeWeight = Math.max(weight, volumeWeight);

类复杂度(Class Complexity):
Cargo类复杂度最高(14),主要因费率计算逻辑复杂;
Flight类复杂度最低(1),职责单一,设计合理。
2. 代码规范问题
方法命名:
存在拼写错误:Carao应为Cargo(多处出现,如Carao.determineRate());
命名不规范:aetFreiaht()应为setFreight()(明显笔误)。
参数传递:
Order类构造方法参数过多(10 个参数),导致可读性差,建议使用构建者模式(Builder Pattern)重构:

点击查看代码
// 原构造方法
public Order(String orderNumber, String orderDate, String senderAddress, 
             String senderName, String senderPhone, String receiverAddress, 
             String receiverName, String receiverPhone, String paymentMethod, 
             Customer customer, Flight flight) { ... }

优化后:

点击查看代码
public static class Builder { /* 逐步设置参数 */ }
Order order = new Order.Builder().withOrderNumber(...).withCustomer(...).build();

性能与扩展性分析 1. 性能瓶颈点 循环效率: 在main函数中遍历货物校验载重量时,使用普通for循环而非增强型for循环,虽无明显性能差异,但代码可读性稍差。 数据结构选择: 使用LinkedHashMap存储货物明细,插入顺序保持性好,但查询效率与HashMap相近,无明显性能问题。 2. 扩展性缺陷 新增货物类型成本高: 目前货物类型通过Cargo类的determineRate()方法中的分支判断实现,若新增 “冷链货物” 等类型,需修改现有代码,违反开闭原则。 改进方案:
点击查看代码
// 定义策略接口
public interface CargoRateStrategy {
    double calculateRate(double chargeWeight);
}
// 具体策略实现
public class NormalCargoStrategy implements CargoRateStrategy { ... }
// Cargo类依赖策略接口
private CargoRateStrategy rateStrategy;
public double getRate() { return rateStrategy.calculateRate(chargeWeight); }
2. 关键代码改进分析 多态实现费率计算:
点击查看代码
abstract class Cargo {
    abstract double getRate(); // 抽象费率获取方法
}
class ExpediteCargo extends Cargo {
    @Override
    public double getRate() {
        return 40; // 加急货物固定费率
    }
}

优点:将费率计算逻辑封装到具体货物子类中,当新增货物类型时只需创建新子类,无需修改现有代码,符合开闭原则。
问题:当前费率为固定值,实际业务中可能需结合货物重量、运输距离等动态计算,可进一步引入策略接口RateStrategy。
输入处理与异常校验:

点击查看代码
try {
    // 读取客户类型
    String customerType = scanner.next();
    if (!("Individual".equals(customerType) || "Corporate".equals(customerType))) {
        throw new IllegalArgumentException("Invalid customer type");
    }
    // 其他输入校验...
} catch (Exception e) {
    System.out.println("Wrong Format");
    scanner.close();
    return;
}

改进:相比题目集 8,新增输入格式校验(如客户类型、货物类型合法性),使用异常处理机制提升程序健壮性,但对非法输入的提示信息仍不够友好,可提供具体错误原因。
三、踩坑心得
(一)输入处理的 “边界地狱”
问题描述:题目集 8 中未对货物尺寸(宽、长、高)进行非负数校验,导致输入-5 3 4时程序抛出NegativeArraySizeException或计算体积重量为负数。
解决过程:

点击查看代码
// 在Cargo构造方法中增加校验
public Cargo(String id, String name, double width, double length, double height, double weight) {
    if (width <= 0 || length <= 0 || height <= 0 || weight <= 0) {
        throw new IllegalArgumentException("Dimension and weight must be positive");
    }
    // ...其他代码
}

教训:输入校验应遵循 “早校验早报错” 原则,在数据源头(构造方法或 setter 方法)进行合法性检查,避免错误数据流入业务逻辑层。
(二)类职责混乱导致的维护困境
问题描述:题目集 8 的Order类同时承担订单信息管理、货物费用计算和输出格式化功能,代码行数超过 300 行,可读性极差。
重构方案:
提取OrderFormatter类:专门负责订单信息的格式化输出,将getDisplayInfo()中的字符串拼接逻辑转移至此。
分离费用计算逻辑:在Order类中注入CargoFeeCalculator接口,通过策略模式实现不同货物类型的费用计算。

点击查看代码
// 重构后Order类依赖费用计算策略
public class Order {
    private final CargoFeeCalculator feeCalculator;
    public Order(CargoFeeCalculator feeCalculator) {
        this.feeCalculator = feeCalculator;
    }
    public double calculateTotalFee() {
        return cargos.stream().mapToDouble(c -> feeCalculator.calculate(c)).sum();
    }
}

效果:类职责单一化后,代码可维护性显著提升,新增计费规则时只需实现新的CargoFeeCalculator接口。
(三)多态与容器结合的 “类型擦除陷阱”
问题描述:题目集 9 中使用ArrayList存储点、线、面对象时,误将Plane类的display()方法返回值类型定义为void,导致容器遍历时无法统一调用多态方法。
解决过程:
确保所有Element子类(Point、Line、Plane)正确重写display()方法,返回类型一致(均为void)。

点击查看代码
// Plane类正确重写display方法
class Plane extends Element {
    @Override
    public void display() {
        System.out.println("The Plane's color is:" + color);
    }
}

教训:使用容器存储多态对象时,需严格遵循里氏替换原则,确保子类方法签名与父类完全一致,避免运行时ClassCastException。
四、改进建议
(一)代码结构优化
引入设计模式:
策略模式:将货物计费规则、支付方式处理等可变逻辑封装为策略类,如RateStrategy、PaymentStrategy,实现动态切换。
工厂模式:创建CustomerFactory和CargoFactory,根据输入类型生成具体客户或货物对象,避免Main类中大量if-else判断。
分层架构设计:
数据层:负责数据输入输出(如InputHandler、OutputFormatter)。
业务层:处理核心逻辑(如OrderService、FlightService)。
模型层:封装领域对象(Customer、Cargo、Flight等)。
(二)增强代码健壮性
完善异常处理体系:
定义业务异常类(如OverloadException、InvalidInputException),区分系统异常与业务逻辑异常。
使用try-with-resources确保Scanner等资源正确关闭,避免内存泄漏。
单元测试覆盖:
对Cargo类的计费逻辑、Flight类的载重量校验等核心功能编写单元测试,使用 JUnit 测试框架验证边界情况(如计费重量等于体积重量、航班载重量恰好满载)。
(三)提升代码可读性与可维护性
规范命名与注释:
将elsehasdownRequest等 “火星文” 方法名改为hasDownwardRequest,确保见名知意。
在复杂逻辑处添加注释,如Flight类的载重量校验逻辑:

点击查看代码
// 校验当前货物重量是否超过航班剩余载重量,剩余载重量=最大载重量-已装载重量
public boolean checkLoad(double weight) {
    return (maxLoad - currentLoad) >= weight; 
}

使用代码分析工具:
通过 SourceMonitor 检查类的复杂度(如Cargo类的方法复杂度应控制在 15 以内),避免出现 “上帝方法”。
使用 PowerDesigner 实时生成类图,确保代码与设计保持一致,便于团队协作。
五、总结
(一)知识与技能提升
面向对象设计能力:通过两次题目集,深入理解了继承、多态、接口的实际应用场景,能够根据业务需求合理设计类层次结构(如航空货运系统的客户与货物类型继承体系)。
复杂系统建模:学会从需求中提取核心实体(客户、货物、航班、订单),分析实体间的关联关系(如订单与货物的聚合关系、客户与订单的关联关系),并通过 UML 类图可视化设计。
问题调试与优化:掌握了通过异常堆栈跟踪、代码分段测试(如单独测试Cargo类的计费逻辑)定位问题的方法,能够针对代码异味(如过长方法、过高类复杂度)进行重构。
(二)待改进方向
设计模式的深度应用:目前仅停留在基础模式(策略、工厂)的简单使用,对结构型模式(装饰器、适配器)和行为型模式(观察者、模板方法)的应用仍不熟练,需通过阅读设计模式经典案例进一步学习。
性能优化意识:在处理大量货物或航班数据时,未考虑数据结构的效率(如使用LinkedList而非ArrayList导致删除操作耗时较高),未来需学习算法与数据结构优化系统性能。
文档与协作能力:代码注释和类设计文档不够完善,在团队开发中可能导致沟通成本增加,需养成编写设计文档和 API 文档的习惯。
六、结语
题目集 8-9 的训练不仅是对面向对象编程知识的综合检验,更是对系统设计思维和问题解决能力的全面提升。从最初的类设计混乱、代码漏洞百出,到逐步通过重构实现高内聚低耦合的系统架构,每一次踩坑与改进都是宝贵的成长经验。未来将继续深耕设计模式与软件架构,努力写出更优雅、更健壮的代码,以应对复杂的实际项目挑战。嘻嘻嘻。