第二次Blog 航空货运管理系统

前言

与第一次大作业电梯调度程序相比,此次大作业航空货运管理系统在算法设计上要友好许多,其主要考察点是输入及输出的处理。而且又经过了几周的课程学习,无论是对题目的分析,还是程序设计的合理性,都比上次作业要得心应手一些。这次大作业主要是对近期学习的继承与多态等知识进行了运用,以及诸如开闭原则、里氏代换原则、依赖倒置原则等面向对象程序设计原则在实际编程中应如何体现。虽然只进行了两轮迭代,还是令我受益匪浅的。

设计与分析

第一次迭代

题目要求大致如下:
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场所在城市,航班降落机场所在城市,航班
日期,航班最大载重量)
客户填写货运订单并进行支付,需要提供如下信息:
客户信息(姓名,电话号码等)
货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选
航班号,订单日期)
支付方式(支付宝支付、微信支付)

这次题目要求比电梯调度要清晰很多,代码编写上压力也没有那么大。源码如下:

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

/**
 * 客户类 - 存储客户的基本信息
 */
class Customer {
    private String clientId;      // 客户编号
    private String name;          // 客户姓名
    private String phone;         // 客户电话
    private String address;       // 客户地址

    // 构造函数,初始化客户信息
    public Customer(String clientId, String name, String phone, String address) {
        this.clientId = clientId;
        this.name = name;
        this.phone = phone;
        this.address = address;
    }

    // 获取客户姓名
    public String getName() {
        return name;
    }

    // 获取客户电话
    public String getPhone() {
        return phone;
    }
}

/**
 * 货物类 - 计算货物的体积重量和计费重量
 */
class Goods {
    private String goodsId;       // 货物编号
    private String name;          // 货物名称
    private int width;            // 货物宽度(cm)
    private int length;           // 货物长度(cm)
    private int height;           // 货物高度(cm)
    private double weight;        // 货物实际重量(kg)

    // 构造函数,初始化货物信息
    public Goods(String goodsId, String name, int width, int length, int height, double weight) {
        this.goodsId = goodsId;
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.weight = weight;
    }

    // 获取货物名称
    public String getName() {
        return name;
    }

    // 计算体积重量(kg):体积(cm³)/6000
    public double getVolumeWeight() {
        return (width * length * height) / 6000.0;
    }

    // 获取计费重量:体积重量和实际重量中的较大值
    public double getChargeWeight() {
        return Math.max(getVolumeWeight(), weight);
    }
}

/**
 * 航班类 - 管理航班的基本信息和载重量
 */
class Flight {
    private String flightNumber;      // 航班号
    private String departureAirport;  // 起飞机场
    private String arrivalAirport;    // 降落机场
    private String date;              // 航班日期
    private double maxLoad;           // 最大载重量(kg)
    private double usedLoad;          // 已使用载重量(kg)

    // 构造函数,初始化航班信息
    public Flight(String flightNumber, String departureAirport, String arrivalAirport, 
                  String date, double maxLoad) {
        this.flightNumber = flightNumber;
        this.departureAirport = departureAirport;
        this.arrivalAirport = arrivalAirport;
        this.date = date;
        this.maxLoad = maxLoad;
        this.usedLoad = 0.0;
    }

    // 获取航班号
    public String getFlightNumber() {
        return flightNumber;
    }

    // 检查航班是否能承载指定重量的货物
    public boolean canCarry(double totalWeight) {
        if (usedLoad + totalWeight <= maxLoad) {
            usedLoad += totalWeight;  // 更新已使用载重量
            return true;
        }
        return false;
    }
}

/**
 * 订单类 - 管理订单的详细信息和计算费用
 */
class Order {
    private String orderId;           // 订单编号
    private String orderDate;         // 订单日期
    private String senderAddress;     // 发件人地址
    private String senderName;        // 发件人姓名
    private String senderPhone;       // 发件人电话
    private String receiverAddress;   // 收件人地址
    private String receiverName;      // 收件人姓名
    private String receiverPhone;     // 收件人电话
    private List<Goods> goodsList;    // 货物列表
    private Flight flight;            // 关联航班

    // 构造函数,初始化订单信息
    public Order(String orderId, String orderDate, String senderAddress, String senderName, 
                 String senderPhone, String receiverAddress, String receiverName, 
                 String receiverPhone, List<Goods> goodsList, Flight flight) {
        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.goodsList = goodsList;
        this.flight = flight;
    }

    // 计算订单中所有货物的总计费重量
    public double calculateTotalWeight() {
        double total = 0.0;
        for (Goods goods : goodsList) {
            total += goods.getChargeWeight();
        }
        return total;
    }

    // 计算订单详情:包括每件货物的费用和总费用
    public Map<String, Object> calculateDetails(Map<String, Double> rateTable) {
        List<Map<String, Object>> details = new ArrayList<>();  // 存储每件货物的明细
        double totalFee = 0.0;  // 总费用
        int sequence = 1;       // 明细编号

        for (Goods goods : goodsList) {
            double chargeWeight = goods.getChargeWeight();  // 获取计费重量
            double rate = rateTable.getOrDefault(goods.getName(), 20.0);  // 获取费率
            double fee = chargeWeight * rate;  // 计算单件货物费用
            totalFee += fee;  // 累加总费用

            // 存储单件货物的明细信息
            Map<String, Object> detail = new HashMap<>();
            detail.put("sequence", sequence++);  // 明细编号
            detail.put("name", goods.getName());  // 货物名称
            detail.put("chargeWeight", chargeWeight);  // 计费重量
            detail.put("rate", rate);  // 计费费率
            detail.put("fee", fee);  // 应交运费
            details.add(detail);
        }

        // 返回总费用和明细列表
        Map<String, Object> result = new HashMap<>();
        result.put("totalFee", totalFee);
        result.put("details", details);
        return result;
    }

    // 以下是获取订单各属性的getter方法
    public String getOrderId() {
        return orderId;
    }

    public String getOrderDate() {
        return orderDate;
    }

    public String getSenderName() {
        return senderName;
    }

    public String getSenderPhone() {
        return senderPhone;
    }

    public String getSenderAddress() {
        return senderAddress;
    }

    public String getReceiverName() {
        return receiverName;
    }

    public String getReceiverPhone() {
        return receiverPhone;
    }

    public String getReceiverAddress() {
        return receiverAddress;
    }

    public Flight getFlight() {
        return flight;
    }
}

/**
 * 主类 - 程序入口点,处理输入输出和业务流程
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取客户信息
        String clientId = scanner.nextLine().trim();
        String clientName = scanner.nextLine().trim();
        String clientPhone = scanner.nextLine().trim();
        String clientAddress = scanner.nextLine().trim();
        int numGoods = Integer.parseInt(scanner.nextLine());

        // 读取货物信息
        List<Goods> goodsList = new ArrayList<>();
        for (int i = 0; i < numGoods; i++) {
            String goodsId = scanner.nextLine().trim();
            String goodsName = scanner.nextLine().trim();
            int width = Integer.parseInt(scanner.nextLine());
            int length = Integer.parseInt(scanner.nextLine());
            int height = Integer.parseInt(scanner.nextLine());
            double weight = Double.parseDouble(scanner.nextLine());
            goodsList.add(new Goods(goodsId, goodsName, width, length, height, weight));
        }

        // 读取航班信息
        String flightNumber = scanner.nextLine().trim();
        String departureAirport = scanner.nextLine().trim();
        String arrivalAirport = scanner.nextLine().trim();
        String flightDate = scanner.nextLine().trim();
        double maxLoad = Double.parseDouble(scanner.nextLine());
        Flight flight = new Flight(flightNumber, departureAirport, arrivalAirport, flightDate, maxLoad);

        // 读取订单信息
        String orderId = scanner.nextLine().trim();
        String orderDate = scanner.nextLine().trim();
        String senderAddress = scanner.nextLine().trim();
        String senderName = scanner.nextLine().trim();
        String senderPhone = scanner.nextLine().trim();
        String receiverAddress = scanner.nextLine().trim();
        String receiverName = scanner.nextLine().trim();
        String receiverPhone = scanner.nextLine().trim();

        // 创建订单对象
        Order order = new Order(orderId, orderDate, senderAddress, senderName, senderPhone,
                receiverAddress, receiverName, receiverPhone, goodsList, flight);

        // 计算订单总重量
        double totalWeight = order.calculateTotalWeight();

        // 检查航班是否能承载该订单
        if (!flight.canCarry(totalWeight)) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.", flightNumber);
            return;  // 航班超载,终止程序
        }

        // 设置不同货物的计费费率
        Map<String, Double> rateTable = new HashMap<>();
        rateTable.put("发电机", 25.0);    // 发电机费率:25元/公斤
        rateTable.put("信号发生器", 30.0);  // 信号发生器费率:30元/公斤

        // 计算订单详情(总费用和每件货物的明细)
        Map<String, Object> detailsResult = order.calculateDetails(rateTable);
        double totalFee = (double) detailsResult.get("totalFee");  // 总费用
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> details = (List<Map<String, Object>>) detailsResult.get("details");  // 货物明细

        // 输出订单信息
        System.out.printf("客户:%s(%s)订单信息如下:\n", clientName, clientPhone);
        System.out.println("-----------------------------------------");
        System.out.printf("航班号:%s\n", order.getFlight().getFlightNumber());
        System.out.printf("订单号:%s\n", order.getOrderId());
        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("微信支付金额:%.1f\n", totalFee);

        // 输出货物明细
        System.out.println("\n货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号    货物名称    计费重量    计费费率    应交运费");
        for (Map<String, Object> detail : details) {
            int sequence = (int) detail.get("sequence");
            String name = (String) detail.get("name");
            double chargeWeight = (double) detail.get("chargeWeight");
            double rate = (double) detail.get("rate");
            double fee = (double) detail.get("fee");
            System.out.printf("%-6d%-12s%8.1f%10.1f%10.1f\n", sequence, name, chargeWeight, rate, fee);
        }
    }
}

核心类设计

Customer 类
设计目的:存储客户的基本信息,包括客户编号、姓名、电话和地址。
设计特点:
封装了客户的属性,提供了基本的访问方法。构造函数初始化所有属性,确保对象创建时状态完整。提供了获取客户姓名和电话的方法,符合封装原则。
Goods 类
设计目的:计算货物的体积重量和计费重量。
设计特点:
包含货物的基本属性如编号、名称、尺寸和实际重量。
核心业务逻辑:计算体积重量和计费重量。体积重量计算公式为体积 (cm³)/6000,计费重量取体积重量和实际重量中的较大值。提供了获取货物名称、体积重量和计费重量的方法。
Flight 类
设计目的:管理航班的基本信息和载重量。
设计特点:
包含航班的基本信息如航班号、起降机场、日期和最大载重量。提供了检查航班是否能承载指定重量货物的方法canCarry,并在成功承载后更新已使用载重量。这种设计确保了航班载重量的一致性和正确性。
Order 类
设计目的:管理订单的详细信息和计算费用。
设计特点:
包含订单的所有相关信息,如订单编号、日期、收发件人信息、货物列表和关联航班。
提供了计算订单总重量的方法calculateTotalWeight。
核心业务逻辑:计算订单详情的方法calculateDetails,根据费率表计算每件货物的费用和总费用,并返回详细信息。提供了获取订单各属性的方法,方便信息展示和使用。

核心方法设计

Goods 类中的 getVolumeWeight () 和 getChargeWeight ()
方法逻辑:
getVolumeWeight():根据货物的长宽高计算体积重量,公式为体积 (cm³)/6000。
getChargeWeight():返回体积重量和实际重量中的较大值作为计费重量。
设计优点:将业务逻辑封装在货物类中,符合单一职责原则,使代码更易维护和扩展。
Flight 类中的 canCarry ()
方法逻辑:检查航班剩余载重量是否足够承载指定重量的货物,如果足够则更新已使用载重量并返回 true,否则返回 false。
设计优点:将航班载重量的管理逻辑封装在航班类中,确保了数据的一致性和完整性。
Order 类中的 calculateTotalWeight () 和 calculateDetails ()
方法逻辑:
calculateTotalWeight():遍历订单中的所有货物,累加每件货物的计费重量得到订单总重量。
calculateDetails():根据费率表计算每件货物的费用和总费用,返回包含详细信息的 Map。
设计优点:将订单费用计算的复杂逻辑封装在订单类中,使订单类成为处理订单业务的核心类,提高了代码的内聚性。

类图

image

复杂度分析

image

规模与结构:共 312 行代码,包含 5 个类(含接口),平均每个类约 2.8 个方法,每个方法平均 10.29 条语句,结构简洁,类与方法数量适中,逻辑分布均匀。
可读性与维护性:注释行占比 24%,提供了良好的代码说明;分支语句占比仅 2.5%,条件逻辑简单,方法调用语句(59 条)和块深度(平均 1.26,最大 3)均较低,代码逻辑清晰,嵌套层次浅,易于理解与维护。
复杂度:最大复杂度为 0,平均复杂度 0.00,表明代码中无复杂的条件 / 循环嵌套,逻辑流线性化,降低了维护难度。块直方图显示多数代码块深度≤2,进一步验证了代码结构的扁平化。

整体而言,代码遵循面向对象设计原则,类职责明确(如Order处理订单逻辑、Flight管理载重量等),耦合度低,内聚性高,在代码度量指标上表现优异,具备良好的可维护性与扩展性,适合后续功能迭代与优化。

第二次迭代

第二次题目在第一次的继承上要求使用继承与多态来编写代码,主要在用户类、支付方式类以及货物类上可以进行扩展。源码如下:

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

// 抽象客户基类,定义客户的基本属性和行为
abstract class Customer {
    protected String type;      // 客户类型(个人或集团)
    protected String id;        // 客户ID
    protected String name;      // 客户姓名
    protected String phone;     // 客户电话
    protected String address;   // 客户地址

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

    // 获取客户折扣率的抽象方法
    public abstract double getDiscountRate();

    // 获取客户姓名
    public String getName() {
        return name;
    }

    // 获取客户电话
    public String getPhone() {
        return phone;
    }
}

// 个人客户类,继承自Customer,折扣率9折
class IndividualCustomer extends Customer {
    public IndividualCustomer(String id, String name, String phone, String address) {
        super("Individual", id, name, phone, address);
    }

    @Override
    public double getDiscountRate() {
        return 0.9;
    }
}

// 集团客户类,继承自Customer,折扣率8折
class CorporateCustomer extends Customer {
    public CorporateCustomer(String id, String name, String phone, String address) {
        super("Corporate", id, name, phone, address);
    }

    @Override
    public double getDiscountRate() {
        return 0.8;
    }
}

// 抽象货物基类,定义货物的基本属性和计算方法
abstract class Cargo {
    protected String id;            // 货物ID
    protected String name;          // 货物名称
    protected double width;         // 货物体宽(厘米)
    protected double length;        // 货物体长(厘米)
    protected double height;        // 货物体高(厘米)
    protected double actualWeight;  // 货物实际重量(千克)

    public Cargo(String id, String name, double width, double length, double height, double actualWeight) {
        this.id = id;
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.actualWeight = actualWeight;
    }

    // 计算体积重量(公式:长×宽×高/6000)
    public double getVolumeWeight() {
        return (width * length * height) / 6000;
    }

    // 获取计费重量(取实际重量和体积重量中的较大值)
    public double getChargeableWeight() {
        return Math.max(actualWeight, getVolumeWeight());
    }

    // 获取货物费率的抽象方法
    public abstract double getRate();
}

// 普通货物类,继承自Cargo
class NormalCargo extends Cargo {
    public NormalCargo(String id, String name, double width, double length, double height, double actualWeight) {
        super(id, name, width, length, height, actualWeight);
    }

    // 根据计费重量计算普通货物的费率
    @Override
    public double getRate() {
        double cw = getChargeableWeight();
        if (cw < 20) return 35;
        else if (cw < 50) return 30;
        else if (cw < 100) return 25;
        else return 15;
    }
}

// 加急货物类,继承自Cargo,费率高于普通货物
class ExpediteCargo extends Cargo {
    public ExpediteCargo(String id, String name, double width, double length, double height, double actualWeight) {
        super(id, name, width, length, height, actualWeight);
    }

    // 根据计费重量计算加急货物的费率
    @Override
    public double getRate() {
        double cw = getChargeableWeight();
        if (cw < 20) return 60;
        else if (cw < 50) return 50;
        else if (cw < 100) return 40;
        else return 30;
    }
}

// 危险货物类,继承自Cargo,费率高于普通和加急货物
class DangerousCargo extends Cargo {
    public DangerousCargo(String id, String name, double width, double length, double height, double actualWeight) {
        super(id, name, width, length, height, actualWeight);
    }

    // 根据计费重量计算危险货物的费率
    @Override
    public double getRate() {
        double cw = getChargeableWeight();
        if (cw < 20) return 80;
        else if (cw < 50) return 50;
        else if (cw < 100) return 30;
        else return 20;
    }
}

// 航班信息类,存储航班基本信息并提供载重检查功能
class Flight {
    private String flightNumber;      // 航班号
    private String departureAirport;  // 出发机场
    private String arrivalAirport;    // 到达机场
    private String flightDate;        // 航班日期
    private double maxLoadCapacity;   // 最大载重(千克)

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

    // 检查航班是否超载
    public boolean isOverloaded(double totalWeight) {
        return totalWeight > maxLoadCapacity;
    }

    // 获取航班号
    public String getFlightNumber() {
        return flightNumber;
    }
}

// 联系方式类,存储联系人的基本信息
class Contact {
    private String name;     // 联系人姓名
    private String phone;    // 联系人电话
    private String address;  // 联系人地址

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

    // 获取联系人姓名
    public String getName() { return name; }
    // 获取联系人电话
    public String getPhone() { return phone; }
    // 获取联系人地址
    public String getAddress() { return address; }
}

// 支付方式枚举,包含支付方式及其中文名
enum PaymentMethod {
    Wechat("微信"), 
    ALiPay("支付宝"), 
    Cash("现金");
    
    private final String chineseName;  // 支付方式中文名

    PaymentMethod(String chineseName) {
        this.chineseName = chineseName;
    }

    // 获取支付方式中文名
    public String getChineseName() {
        return chineseName;
    }
}

// 订单类,整合订单相关的所有信息并提供费用计算功能
class Order {
    private String orderId;           // 订单ID
    private String orderDate;         // 订单日期
    private Contact sender;           // 发件人信息
    private Contact receiver;         // 收件人信息
    private PaymentMethod paymentMethod;  // 支付方式
    private Customer customer;        // 客户信息
    private Flight flight;            // 航班信息
    private List<Cargo> cargos;       // 货物列表

    public Order(String orderId, String orderDate, Contact sender, Contact receiver, 
                PaymentMethod paymentMethod, Customer customer, Flight flight, List<Cargo> cargos) {
        this.orderId = orderId;
        this.orderDate = orderDate;
        this.sender = sender;
        this.receiver = receiver;
        this.paymentMethod = paymentMethod;
        this.customer = customer;
        this.flight = flight;
        this.cargos = cargos;
    }

    // 计算订单总计费重量
    public double getTotalChargeableWeight() {
        double total = 0.0;
        for (Cargo cargo : cargos) {
            total += cargo.getChargeableWeight();
        }
        return total;
    }

    // 计算订单总支付金额(考虑客户折扣)
    public double getTotalPayment() {
        double totalBasic = 0.0;
        for (Cargo cargo : cargos) {
            totalBasic += cargo.getChargeableWeight() * cargo.getRate();
        }
        return totalBasic * customer.getDiscountRate();
    }

    // 获取订单ID
    public String getOrderId() { return orderId; }
    // 获取订单日期
    public String getOrderDate() { return orderDate; }
    // 获取发件人信息
    public Contact getSender() { return sender; }
    // 获取收件人信息
    public Contact getReceiver() { return receiver; }
    // 获取支付方式
    public PaymentMethod getPaymentMethod() { return paymentMethod; }
    // 获取航班信息
    public Flight getFlight() { return flight; }
    // 获取货物列表
    public List<Cargo> getCargos() { return cargos; }
}

// 输入处理类,负责从控制台读取各类信息并创建相应对象
class Input {
    // 读取客户信息并创建客户对象
    public static Customer readCustomer(Scanner scanner) {
        String customerType = scanner.next();
        String customerId = scanner.next();
        String customerName = scanner.next();
        String customerPhone = scanner.next();
        String customerAddress = scanner.next();
        
        return "Corporate".equals(customerType) ? 
            new CorporateCustomer(customerId, customerName, customerPhone, customerAddress) :
            new IndividualCustomer(customerId, customerName, customerPhone, customerAddress);
    }

    // 读取货物信息并创建货物列表
    public static List<Cargo> readCargos(Scanner scanner) {
        String cargoType = scanner.next();
        int cargoCount = scanner.nextInt();
        List<Cargo> cargos = new ArrayList<>();
        
        for (int i = 0; i < cargoCount; i++) {
            String cargoId = scanner.next();
            String cargoName = scanner.next();
            double width = scanner.nextDouble();
            double length = scanner.nextDouble();
            double height = scanner.nextDouble();
            double actualWeight = scanner.nextDouble();
            
            switch (cargoType) {
                case "Normal":
                    cargos.add(new NormalCargo(cargoId, cargoName, width, length, height, actualWeight));
                    break;
                case "Expedite":
                    cargos.add(new ExpediteCargo(cargoId, cargoName, width, length, height, actualWeight));
                    break;
                default:
                    cargos.add(new DangerousCargo(cargoId, cargoName, width, length, height, actualWeight));
            }
        }
        return cargos;
    }

    // 读取航班信息并创建航班对象
    public static Flight readFlight(Scanner scanner) {
        String flightNumber = scanner.next();
        String departureAirport = scanner.next();
        String arrivalAirport = scanner.next();
        String flightDate = scanner.next();
        double maxLoadCapacity = scanner.nextDouble();
        return new Flight(flightNumber, departureAirport, arrivalAirport, flightDate, maxLoadCapacity);
    }

    // 读取联系人信息并创建联系人对象
    public static Contact readContact(Scanner scanner) {
        String address = scanner.next();
        String name = scanner.next();
        String phone = scanner.next();
        return new Contact(name, phone, address);
    }
}

// 输出处理类,负责格式化并输出各类信息
class Output {
    // 打印航班超载信息
    public static void printOverloadMessage(String flightNumber) {
        System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.", flightNumber);
    }

    // 打印订单基本信息
    public static void printOrderInfo(Customer customer, Order order, double totalWeight) {
        System.out.printf("客户:%s(%s)订单信息如下:%n", customer.getName(), customer.getPhone());
        System.out.println("-----------------------------------------");
        System.out.printf("航班号:%s%n", order.getFlight().getFlightNumber());
        System.out.printf("订单号:%s%n", order.getOrderId());
        System.out.printf("订单日期:%s%n", order.getOrderDate());
        printContactInfo("发件人", order.getSender());
        printContactInfo("收件人", order.getReceiver());
        System.out.printf("订单总重量(kg):%.1f%n", totalWeight);
        System.out.printf("%s支付金额:%.1f%n", 
            order.getPaymentMethod().getChineseName(), 
            order.getTotalPayment());
    }

    // 打印联系人信息(私有方法,供内部调用)
    private static void printContactInfo(String type, Contact contact) {
        System.out.printf("%s姓名:%s%n", type, contact.getName());
        System.out.printf("%s电话:%s%n", type, contact.getPhone());
        System.out.printf("%s地址:%s%n", type, contact.getAddress());
    }

    // 打印货物明细信息
    public static void printCargoDetails(List<Cargo> cargos) {
        System.out.println("\n货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
        for (int i = 0; i < cargos.size(); i++) {
            Cargo c = cargos.get(i);
            double cw = c.getChargeableWeight();
            double rate = c.getRate();
            double fee = cw * rate;
            System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f%n", 
                i + 1, c.name, cw, rate, fee);
        }
    }
}

// 主类,程序入口点
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取客户信息
        Customer customer = Input.readCustomer(scanner);
        
        // 读取货物信息
        List<Cargo> cargos = Input.readCargos(scanner);
        
        // 读取航班信息
        Flight flight = Input.readFlight(scanner);
        
        // 读取订单信息
        String orderId = scanner.next();
        String orderDate = scanner.next();
        Contact sender = Input.readContact(scanner);
        Contact receiver = Input.readContact(scanner);
        PaymentMethod paymentMethod = PaymentMethod.valueOf(scanner.next());
        
        // 创建订单对象
        Order order = new Order(orderId, orderDate, sender, receiver, 
            paymentMethod, customer, flight, cargos);

        // 载重检查
        double totalWeight = order.getTotalChargeableWeight();
        if (flight.isOverloaded(totalWeight)) {
            Output.printOverloadMessage(flight.getFlightNumber());
            return;
        }

        // 输出订单信息
        Output.printOrderInfo(customer, order, totalWeight);
        
        // 输出货物明细
        Output.printCargoDetails(cargos);

        scanner.close();
    }
}


核心类设计

客户类层次结构
Customer 作为抽象基类,定义了客户的基本属性(类型、ID、姓名等)和抽象方法 getDiscountRate()。
IndividualCustomer 和 CorporateCustomer 继承自 Customer,分别实现个人客户(9 折)和集团客户(8 折)的折扣策略。
货物类层次结构
Cargo 作为抽象基类,封装了货物的基本属性(ID、长宽高、重量等),提供了体积重量和计费重量的计算方法,并定义抽象方法 getRate()。
具体子类 NormalCargo、ExpediteCargo 和 DangerousCargo 实现不同的费率计算逻辑,根据计费重量分档定价。
订单核心类
Order 类整合了订单的所有信息(客户、航班、货物列表、收发货人等),负责计算总重量和总费用。
采用组合模式关联 Customer、Flight、Contact 等对象,实现订单信息的统一管理。
辅助类
Flight 管理航班信息并提供载重检查功能。
Contact 存储收发货人的联系信息。
PaymentMethod 枚举封装支付方式及其中文名。
输入输出处理类
Input 类负责从控制台读取数据并创建对象,将输入逻辑与业务逻辑分离。
Output 类负责格式化输出订单信息和货物明细,遵循单一职责原则。

核心方法设计

多态实现的费率计算
Cargo 类的 getRate() 方法在子类中被重写,不同类型的货物根据计费重量采用不同的费率策略。
例如,普通货物根据重量分为 4 档费率(35 元 /kg 到 15 元 /kg),加急和危险货物有各自的费率体系。
订单费用计算
Order.getTotalPayment() 方法结合客户折扣和货物基础费用,计算公式为:总基础费用 × 客户折扣率。
总基础费用由各货物的计费重量和费率乘积累加得到。
载重检查机制
Flight.isOverloaded() 方法检查订单总重量是否超过航班最大载重,确保业务规则的执行。
输入数据处理
Input 类的静态方法(如 readCustomer()、readCargos())负责解析控制台输入并创建对象,处理类型转换和参数验证。
格式化输出
Output 类的方法(如 printOrderInfo()、printCargoDetails())使用格式化字符串输出订单信息,确保显示内容的规范性。

类图

image

复杂度分析

image

规模与结构:
共 412 行代码,包含 6 个类(含接口),平均每个类约 3.17 个方法,每个方法平均 6.37 条语句。类与方法数量适中,方法内逻辑简洁,结构扁平化,便于功能扩展与维护。
可读性与维护性:
注释行占比 11.7%,提供基础代码说明;分支语句占比 10.4%,条件逻辑简单。平均块深度 1.16(最大 3),嵌套层次浅,代码逻辑清晰。方法调用语句(34 条)反映类间交互适度,整体代码易于理解与修改。
复杂度:
平均复杂度 1.56,最大复杂度 5(集中在NormalCargo.getRate()方法,行号 94),存在中等复杂度逻辑,需关注该方法的优化(如简化条件判断、提取子逻辑)。块直方图显示多数代码块深度≤2,进一步验证结构简洁性。
总结:
代码遵循面向对象设计,类职责明确(如Goods计算重量、Flight管理载重量、Order处理订单逻辑),耦合度低。虽存在局部中等复杂度方法(需优化),但整体可维护性与扩展性良好。后续可针对NormalCargo.getRate()方法进行重构,降低圈复杂度,提升代码质量。

整体而言,代码结构合理,注释与块深度指标优异,适合迭代开发,通过优化复杂方法可进一步提升代码健壮性。

心得

就我个人而言,这次题目的压迫感是没有上一次大的。如果说上次大作业我们的角色是thinker,那么这一次我们就更接近designer。上次可谓是尸横遍野,一方面大家是第一次接触这样的大作业,无论是算法设计还是类设计上都略显稚嫩,题目本身难度也不小。在老师提供了许多解析,以及开放讨论区、通过同学之间互相交流讨论之后,我才艰难的度过了这一关。也正是经过上一次的洗礼,在面对这次大作业时,我显得更加从容。第二次迭代让我切身体会到继承在代码功能扩展上带来的便利,以及各种面向对象编程原则在实际编程中的体现:Cargo子类新增货物类型时,Customer子类新增客户类型时,无需修改基类,只需扩展子类(开闭原则);Order类依赖于抽象(Customer和Cargo 接口),而非具体实现(依赖倒置原则);Customer子类IndividualCustomer 和 CorporateCustomer)可以替代基类 Customer 使用,例如在 Order 类中作为客户类型的参数(里氏代换原则)等。
当然我的设计还是存在不小问题的。第一次迭代的程序的Main方法太冗杂了,输入输出、创建订单等操作全部塞在里面;在进行第二次迭代时,我把输入输出单独提出来作为两个类,让代码尽量更贴近单一职责原则。而且两次代码在处理不同客户、不同货物时采用了较多的分支结构,可以通过运用简单的工厂结构来避免出现这样的问题。
这次大作业也让我尝到了不拖延的甜头,尽管第一次提交出现了种种错误,什么格式错误,非零返回啊乱七八糟的,但是因为开始的早,也有充足的时间来进行不断地修改。而且这次的错误更多是在格式上以及异常输入处理上出现的问题,并非算法本身逻辑出现问题,修改起来也较为简单。

总结

本次大作业训练的点就是继承与多态的运用,以及在面对要对对象进行操作的要求使运用容器类来处理对象。我也对老师上课讲的继承绝对不是用来复用而是来方便扩展有了更深的认识。如果在第一次迭代中就保证了代码的可扩展性,那么第二次作业也就是纯a piece of cake。希望下次还能较为顺利的完成大作业!!!

建议

总感觉没啥好提的建议啊,挺好的。感谢天感谢地感谢生我养我的父母感谢java课程组。🆗那就这样了!!!

posted @ 2025-05-25 22:18  OnlyBlueeeeee  阅读(18)  评论(0)    收藏  举报