第二次Blog作业

航空货运管理系统

前言

第一次系统迭代作业 (噩梦) 结束了,来到了第二次的迭代作业,这次系统迭代作业的重点不在于算法,而是考验起了设计能力,对我来说算不上难,也算不上简单吧。
以下是我总结的遇到的知识点:

  • 类与对象
    • 实体类封装:CustomerInfo,CargoInfo,FlightInfo等类封装属性与业务方法,如CargoInfo类计算计费重量
    • 组合关系:Order类组合DeliveryInfo,FlightInfo,List等,体现 “整体 - 部分” 关系
  • 接口与实现
    • 策略接口
      • RateStrategy定义统一方法getRate(),不同策略类,如RateStrategy1-RateStrategy12的实现具体逻辑
      • PaymentMethod定义pay()方法,实现类(WechatPayment等)处理不同支付逻辑
    • 接口隔离原则
      • 将费率计算与支付逻辑分离为独立接口,降低模块耦合

设计与分析

题目集08

  • 题目
点击展开题目

某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包
括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。
本次作业主要考虑货物重量/体积,以下是具体的计算方式和关键要点:

一、计费重量的确定
空运以实际重量(GrossWeight)和体积重量(VolumeWeight)中的较
高者作为计费重量。
计算公式:
体积重量(kg)= 货物体积(长×宽×高,单位:厘米)÷6000
示例:
若货物实际重量为80kg,体积为120cm×80cm×60cm,则:
体积重量 =(120×80×60)÷6000=96kg
计费重量取96kg(因96kg>80kg)。

二、基础运费计算
费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如
CNY 30/kg)。本次作业费率采用分段计算方式
公式:基础运费 = 计费重量 × 费率

三、题目说明
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场所在城市,航班降落机场所在城市,航班
日期,航班最大载重量)
客户填写货运订单并进行支付,需要提供如下信息:
客户信息(姓名,电话号码等)
货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选
航班号,订单日期)
支付方式(支付宝支付、微信支付)
注:一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独
计费。
程序需要从键盘依次输入填写订单需要提供的信息,然后分别生成订单信
息报表及货物明细报表。

四、题目要求
本次题目重点考核面向对象设计原则中的单一职责原则、里氏代换原则、开
闭原则以及合成复用原则,除需要在PTA平台提交源码外,还需要在超星平台
提交本次作业最终得分源码(首次提交最高分源码)的类图,评判标准为:
基础得分:PTA实际得分
设计因素:单一职责原则(40%)、里氏代换原则(20%)、开闭原则(20%)、合成复用原则(20%)
最终得分:基础得分扣减所有违背设计原则分值(违背所有四个原则的设计
最终得分为0分)
注:提交源码时务必慎重,一定核查是否符合本次题目考核的四个设计原
则,否则一旦提交得到满分则无法修改。

  • 源码
点击展开代码
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        try {
            // 客户信息
            String customerId = readLine(scanner);
            String customerName = readLine(scanner);
            String customerPhone = readLine(scanner);
            String customerAddress = readLine(scanner);
            CustomerInfo customer = new CustomerInfo(customerId, customerName, customerPhone, customerAddress);

            // 货物数量
            int cargoCount = Integer.parseInt(readLine(scanner));
            List<OrderItem> items = new ArrayList<>();

            // 货物信息
            for (int i = 0; i < cargoCount; i++) {
                String cargoId = readLine(scanner);
                String cargoName = readLine(scanner);
                double width = Double.parseDouble(readLine(scanner));
                double length = Double.parseDouble(readLine(scanner));
                double height = Double.parseDouble(readLine(scanner));
                double grossWeight = Double.parseDouble(readLine(scanner));
                items.add(new OrderItem(new CargoInfo(cargoName, width, length, height, grossWeight)));
            }

            // 航班信息
            String flightNo = readLine(scanner);
            String departureAirport = readLine(scanner);
            String arrivalAirport = readLine(scanner);
            Date flightDate = sdf.parse(readLine(scanner));  // 航班日期
            double maxWeight = Double.parseDouble(readLine(scanner));  // 最大载重量
            FlightInfo flight = new FlightInfo(flightNo, departureAirport, arrivalAirport, flightDate, maxWeight);

            // 订单信息
            String orderNo = readLine(scanner);
            Date orderDate = sdf.parse(readLine(scanner));  // 订单日期
            String senderAddress = readLine(scanner);       // 发件人地址
            String senderName = readLine(scanner);          // 发件人姓名
            String senderPhone = readLine(scanner);         // 发件人电话
            String recipientAddress = readLine(scanner);    // 收件人地址
            String recipientName = readLine(scanner);       // 收件人姓名
            String recipientPhone = readLine(scanner);      // 收件人电话

            // 创建订单
            Order order = new Order(
                orderNo, orderDate, 
                new DeliveryInfo(senderName, senderPhone, senderAddress, recipientName, recipientPhone, recipientAddress),
                customer, flight, items, 
                new WechatPayment()  //使用微信支付
            );

            // 校验航班载重
            double totalWeight = order.getTotalChargeableWeight();
            if (totalWeight > flight.getMaxWeight()) {
                System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n", flightNo);
                return;
            }

            System.out.print(order.generateOrderReport());
            System.out.print("\n");
            System.out.print(order.generateCargoDetailReport());

        } catch (Exception e) {
            System.out.println("输入错误:" + e.getMessage());
        } finally {
            scanner.close();
        }
    }

    private static String readLine(Scanner scanner) {
        if (scanner.hasNextLine()) {
            return scanner.nextLine().trim();
        }
        throw new IllegalArgumentException("输入数据不完整,缺少必要行");
    }
}

// 费率策略接口
interface RateStrategy {
    double getRate(double chargeableWeight);
}

class RateStrategy1 implements RateStrategy {
    @Override
    public double getRate(double w) { return 35.0; }
}
class RateStrategy2 implements RateStrategy {
    @Override
    public double getRate(double w) { return 30.0; }
}
class RateStrategy3 implements RateStrategy {
    @Override
    public double getRate(double w) { return 25.0; }
}
class RateStrategy4 implements RateStrategy {
    @Override
    public double getRate(double w) { return 15.0; }
}

// 支付接口
interface PaymentMethod {
    double pay(double amount);
}
class WechatPayment implements PaymentMethod {
    @Override public double pay(double amount) { return amount; } // 微信支付直接返回金额
}

// 核心业务类
class Order {
    private String orderNo;
    private Date orderDate;
    private DeliveryInfo deliveryInfo;
    private CustomerInfo customer;
    private FlightInfo flight;
    private List<OrderItem> items;
    private PaymentMethod paymentMethod;

    public Order(String orderNo, Date orderDate, DeliveryInfo deliveryInfo, 
                CustomerInfo customer, FlightInfo flight, List<OrderItem> items, 
                PaymentMethod paymentMethod) {
        this.orderNo = orderNo;
        this.orderDate = orderDate;
        this.deliveryInfo = deliveryInfo;
        this.customer = customer;
        this.flight = flight;
        this.items = items;
        this.paymentMethod = paymentMethod;
    }

    public double getTotalChargeableWeight() {
        return items.stream().mapToDouble(OrderItem::getChargeableWeight).sum();
    }

    public double calculateTotalFreight() {
        return items.stream().mapToDouble(OrderItem::calculateFreight).sum();
    }

    public String generateOrderReport() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return String.format(
            "客户:%s(%s)订单信息如下:\n" +
            "-----------------------------------------\n" +
            "航班号:%s\n" +
            "订单号:%s\n" +
            "订单日期:%s\n" +
            "发件人姓名:%s\n" +
            "发件人电话:%s\n" +
            "发件人地址:%s\n" +
            "收件人姓名:%s\n" +
            "收件人电话:%s\n" +
            "收件人地址:%s\n" +
            "订单总重量(kg):%.1f\n" +
            "微信支付金额:%.1f\n",
            customer.getName(), customer.getPhone(),
            flight.getFlightNo(),
            orderNo,
            sdf.format(orderDate),
            deliveryInfo.getSenderName(),
            deliveryInfo.getSenderPhone(),
            deliveryInfo.getSenderAddress(),
            deliveryInfo.getRecipientName(),
            deliveryInfo.getRecipientPhone(),
            deliveryInfo.getRecipientAddress(),
            getTotalChargeableWeight(),
            paymentMethod.pay(calculateTotalFreight())
        );
    }

    public String generateCargoDetailReport() {
        StringBuilder sb = new StringBuilder("货物明细如下:\n-----------------------------------------\n");
        // 标题行使用制表符分隔
        sb.append("明细编号\t货物名称\t计费重量\t计费费率\t应交运费\n");
        // 货物行使用制表符分隔,数值保留1位小数
        for (int i = 0; i < items.size(); i++) {
            OrderItem item = items.get(i);
            sb.append(String.format(
                "%d\t%s\t%.1f\t%.1f\t%.1f\n",
                i + 1,
                item.getCargo().getName(),
                item.getChargeableWeight(),
                item.getRate(),
                item.calculateFreight()
            ));
        }
        return sb.toString();
    }
}

class OrderItem {
    private CargoInfo cargo;
    private FreightCalculator calculator;

    public OrderItem(CargoInfo cargo) {
        this.cargo = cargo;
        this.calculator = new FreightCalculator();
        // 根据计费重量选择费率策略
        double weight = cargo.getChargeableWeight();
        if (weight < 20) calculator.setRateStrategy(new RateStrategy1());
        else if (weight < 50) calculator.setRateStrategy(new RateStrategy2());
        else if (weight < 100) calculator.setRateStrategy(new RateStrategy3());
        else calculator.setRateStrategy(new RateStrategy4());
    }

    public double getChargeableWeight() {
        return cargo.getChargeableWeight();
    }

    public double getRate() {
        return calculator.getRateStrategy().getRate(getChargeableWeight());
    }

    public double calculateFreight() {
        return calculator.calculateBaseFreight(getChargeableWeight());
    }

    public CargoInfo getCargo() {
        return cargo;
    }
}

class FreightCalculator {
    private RateStrategy rateStrategy;

    public void setRateStrategy(RateStrategy rateStrategy) {
        this.rateStrategy = rateStrategy;
    }

    public RateStrategy getRateStrategy() {
        return rateStrategy;
    }

    public double calculateBaseFreight(double chargeableWeight) {
        return chargeableWeight * rateStrategy.getRate(chargeableWeight);
    }
}

class CargoInfo {
    private String name;
    private double width;   // cm
    private double length;  // cm
    private double height;  // cm
    private double grossWeight;  // kg

    public CargoInfo(String name, double width, double length, double height, double grossWeight) {
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.grossWeight = grossWeight;
    }

    public double calculateVolumeWeight() {
        return (width * length * height) / 6000;
    }

    public double getChargeableWeight() {
        return Math.max(grossWeight, calculateVolumeWeight());
    }

    public String getName() {
        return name;
    }
}

class FlightInfo {
    private String flightNo;
    private String departureAirport;
    private String arrivalAirport;
    private Date flightDate;
    private double maxWeight;

    public FlightInfo(String flightNo, String departureAirport, String arrivalAirport, Date flightDate, double maxWeight) {
        this.flightNo = flightNo;
        this.departureAirport = departureAirport;
        this.arrivalAirport = arrivalAirport;
        this.flightDate = flightDate;
        this.maxWeight = maxWeight;
    }

    public String getFlightNo() {
        return flightNo;
    }

    public double getMaxWeight() {
        return maxWeight;
    }
}

class DeliveryInfo {
    private String senderName;
    private String senderPhone;
    private String senderAddress;
    private String recipientName;
    private String recipientPhone;
    private String recipientAddress;

    public DeliveryInfo(String senderName, String senderPhone, String senderAddress, 
                       String recipientName, String recipientPhone, String recipientAddress) {
        this.senderName = senderName;
        this.senderPhone = senderPhone;
        this.senderAddress = senderAddress;
        this.recipientName = recipientName;
        this.recipientPhone = recipientPhone;
        this.recipientAddress = recipientAddress;
    }

    public String getSenderName() {
        return senderName;
    }

    public String getSenderPhone() {
        return senderPhone;
    }

    public String getSenderAddress() {
        return senderAddress;
    }

    public String getRecipientName() {
        return recipientName;
    }

    public String getRecipientPhone() {
        return recipientPhone;
    }

    public String getRecipientAddress() {
        return recipientAddress;
    }
}

class CustomerInfo {
    private String id;
    private String name;
    private String phone;
    private String address;

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

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }
}

类图

代码分析

注释依旧过少 (怎么好像每次都有)

解释

  • 基本参数
    • 代码规模:361 行代码,203 条语句,注释行占比 6.9%(偏低,需增强注释可读性)。
    • 结构指标:14 个类 / 接口,平均每个类 2.71 个方法(适中),每个方法平均 2.89 条语句(方法短小,符合单一职责)。
    • 逻辑复杂度:分支语句占比 3.4%(条件判断少,逻辑较线性),方法调用语句 69 条(存在方法交互)。最复杂方法为OrderItem.OrderItem()(行号 196,需检查其逻辑复杂度)。
  • 图形分析
    • Kiviat 图(雷达图):展示注释占比、类方法数、方法平均语句数、最大复杂度、最大深度、平均深度、平均复杂度等指标。其中方法平均语句数极低(2.89),表明方法职责单一,设计良好;需关注最大复杂度和深度
    • Block Histogram(直方图):深度(代码块嵌套层次)分布显示,深度 2-3 的语句最多,9 + 深度几乎无,嵌套复杂度可控(避免过深嵌套)

心得

也算是第一次真正的做较大的系统设计吧,难度比想象中的大了很多,花了很多时间,还好卡点过了,相较于上次迭代作业给了我很大的信心吧

题目集09

  • 题目
点击展开题目

某航空公司“航空货运管理系统”中的空运费的计算涉及多个因素,通常包
括货物重量/体积、运输距离、附加费用、货物类型、客户类型以及市场供需等。
本次作业主要考虑货物重量/体积,以下是具体的计算方式和关键要点:

一、计费重量的确定
空运以实际重量(GrossWeight)和体积重量(VolumeWeight)中的较
高者作为计费重量。
计算公式:
体积重量(kg)= 货物体积(长×宽×高,单位:厘米)÷6000
示例:
若货物实际重量为80kg,体积为120cm×80cm×60cm,则:
体积重量 =(120×80×60)÷6000=96kg
计费重量取96kg(因96kg>80kg)。

二、基础运费计算
费率(Rate):航空公司或货代根据航线、货物类型、市场行情等制定(如
CNY30/kg)。本次作业费率与货物类型有关,货物类型分为普通货物、危险货
物和加急货物三种,其费率分别为:
计算公式:基础运费 = 计费重量 × 费率 × 折扣率
其中,折扣率是指不同的用户类型针对每个订单的运费可以享受相应的折扣,
在本题中,用户分为个人用户和集团用户,其中个人用户可享受订单运费的9
折优惠,集团用户可享受订单运费的8折优惠。

三、题目说明
本次题目模拟某客户到该航空公司办理一次货运业务的过程:
航空公司提供如下信息:
航班信息(航班号,航班起飞机场,航班降落机场,航班日期,航班最大载重量)
客户填写货运订单并进行支付,需要提供如下信息:
客户信息(姓名,电话号码等)
货物信息(货物名称,货物包装长、宽、高尺寸,货物重量等)
运送信息(发件人姓名、电话、地址,收件人姓名、电话、地址,所选
航班号,订单日期)
支付方式(支付宝支付、微信支付、现金支付)
注:一个货运订单可以运送多件货物,每件货物均需要根据重量及费率单独
计费。
程序需要从键盘依次输入填写订单需要提供的信息,然后分别生成订单信
息报表及货物明细报表。

四、题目要求
本次题目重点考核面向对象设计原则中的单一职责原则、里氏代换原则、开
闭原则以及合成复用原则、依赖倒转原则,除需要在PTA平台提交源码外,还
需要在超星平台提交本次作业最终得分源码(首次提交最高分源码)的类图,
评判标准为:
基础得分:PTA实际得分
设计因素:单一职责原则(20%)、里氏代换原则(20%)、开闭原则(20%)、
合成复用原则(20%)、依赖倒转原则(20%)。
最终得分:基础得分扣减所有违背设计原则分值(违背所有五个原则的设计
最终得分为0分)

输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door

  • 源码
点击展开代码
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        try {
            // 客户信息
            String customerType = readLine(scanner);
            String customerId = readLine(scanner);
            String customerName = readLine(scanner);
            String customerPhone = readLine(scanner);
            String customerAddress = readLine(scanner);
            CustomerInfo customer = new CustomerInfo(customerId, customerName, customerPhone, customerAddress, customerType);

            // 货物类型和数量
            String cargoType = readLine(scanner).trim();
            int cargoCount = Integer.parseInt(readLine(scanner).trim());

            List<OrderItem> items = new ArrayList<>();
            // 货物信息
            for (int i = 0; i < cargoCount; i++) {
                String cargoId = readLine(scanner).trim();
                String cargoName = readLine(scanner).trim();
                double width = Double.parseDouble(readLine(scanner).trim());
                double length = Double.parseDouble(readLine(scanner).trim());
                double height = Double.parseDouble(readLine(scanner).trim());
                double grossWeight = Double.parseDouble(readLine(scanner).trim());
                items.add(new OrderItem(new CargoInfo(cargoName, width, length, height, grossWeight, cargoType), customer.getType()));
            }

            // 航班信息
            String flightNo = readLine(scanner).trim();
            String departureAirport = readLine(scanner).trim();
            String arrivalAirport = readLine(scanner).trim();
            Date flightDate = sdf.parse(readLine(scanner).trim());
            double maxWeight = Double.parseDouble(readLine(scanner).trim());
            FlightInfo flight = new FlightInfo(flightNo, departureAirport, arrivalAirport, flightDate, maxWeight);

            // 订单信息
            String orderNo = readLine(scanner).trim();
            Date orderDate = sdf.parse(readLine(scanner).trim());
            String senderAddress = readLine(scanner).trim();
            String senderName = readLine(scanner).trim();
            String senderPhone = readLine(scanner).trim();
            String recipientAddress = readLine(scanner).trim();
            String recipientName = readLine(scanner).trim();
            String recipientPhone = readLine(scanner).trim();
            String paymentType = readLine(scanner).trim();

            // 创建支付方式
            PaymentMethod paymentMethod;
            switch (paymentType) {
                case "Wechat":
                    paymentMethod = new WechatPayment();
                    break;
                case "ALiPay":
                    paymentMethod = new AliPayment();
                    break;
                case "Cash":
                    paymentMethod = new CashPayment();
                    break;
                default:
                    throw new IllegalArgumentException("Invalid payment method: " + paymentType);
            }

            // 创建订单
            Order order = new Order(
                    orderNo, orderDate,
                    new DeliveryInfo(senderName, senderPhone, senderAddress, recipientName, recipientPhone, recipientAddress),
                    customer, flight, items,
                    paymentMethod, paymentType
            );

            // 校验航班载重
            double totalWeight = order.getTotalChargeableWeight();
            if (totalWeight > flight.getMaxWeight()) {
                System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n", flightNo);
                return;
            }

            System.out.printf("%s\n",order.generateOrderReport());
            System.out.print(order.generateCargoDetailReport());

        } catch (Exception e) {
            System.out.println("输入错误:" + e.getMessage());
        } finally {
            scanner.close();
        }
    }

    private static String readLine(Scanner scanner) {
        if (scanner.hasNextLine()) {
            return scanner.nextLine().trim();
        }
        throw new IllegalArgumentException("输入数据不完整,缺少必要行");
    }
}

// 费率策略接口
interface RateStrategy {
    double getRate(double chargeableWeight);
}

class RateStrategy1 implements RateStrategy {
    @Override
    public double getRate(double w) { return 35.0; }
}
class RateStrategy2 implements RateStrategy {
    @Override
    public double getRate(double w) { return 30.0; }
}
class RateStrategy3 implements RateStrategy {
    @Override
    public double getRate(double w) { return 25.0; }
}
class RateStrategy4 implements RateStrategy {
    @Override
    public double getRate(double w) { return 15.0; }
}
class RateStrategy5 implements RateStrategy {
    @Override
    public double getRate(double w) { return 80.0; }
}
class RateStrategy6 implements RateStrategy {
    @Override
    public double getRate(double w) { return 50.0; }
}
class RateStrategy7 implements RateStrategy {
    @Override
    public double getRate(double w) { return 30.0; }
}
class RateStrategy8 implements RateStrategy {
    @Override
    public double getRate(double w) { return 20.0; }
}
class RateStrategy9 implements RateStrategy {
    @Override
    public double getRate(double w) { return 60.0; }
}
class RateStrategy10 implements RateStrategy {
    @Override
    public double getRate(double w) { return 50.0; }
}
class RateStrategy11 implements RateStrategy {
    @Override
    public double getRate(double w) { return 40.0; }
}
class RateStrategy12 implements RateStrategy {
    @Override
    public double getRate(double w) { return 30.0; }
}

// 支付接口
interface PaymentMethod {
    double pay(double amount);
}
class WechatPayment implements PaymentMethod {
    @Override public double pay(double amount) { return amount; }
}
class AliPayment implements PaymentMethod {
    @Override public double pay(double amount) { return amount; }
}
class CashPayment implements PaymentMethod {
    @Override public double pay(double amount) { return amount; }
}

// 核心业务类
class Order {
    private String orderNo;
    private Date orderDate;
    private DeliveryInfo deliveryInfo;
    private CustomerInfo customer;
    private FlightInfo flight;
    private List<OrderItem> items;
    private PaymentMethod paymentMethod;
    private String paymentType;

    public Order(String orderNo, Date orderDate, DeliveryInfo deliveryInfo,
                 CustomerInfo customer, FlightInfo flight, List<OrderItem> items,
                 PaymentMethod paymentMethod, String paymentType) {
        this.orderNo = orderNo;
        this.orderDate = orderDate;
        this.deliveryInfo = deliveryInfo;
        this.customer = customer;
        this.flight = flight;
        this.items = items;
        this.paymentMethod = paymentMethod;
        this.paymentType = paymentType;
    }

    public double getTotalChargeableWeight() {
        return items.stream().mapToDouble(OrderItem::getChargeableWeight).sum();
    }

    public double calculateTotalFreight() {
        return items.stream().mapToDouble(item -> item.calculateFreight(customer.getType())).sum();
    }

    public String generateOrderReport() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String paymentName = switch (paymentType) {
            case "Wechat" -> "微信";
            case "ALiPay" -> "支付宝";
            case "Cash" -> "现金";
            default -> paymentType;
        };
        return String.format(
                "客户:%s(%s)订单信息如下:\n" +
                        "-----------------------------------------\n" +
                        "航班号:%s\n" +
                        "订单号:%s\n" +
                        "订单日期:%s\n" +
                        "发件人姓名:%s\n" +
                        "发件人电话:%s\n" +
                        "发件人地址:%s\n" +
                        "收件人姓名:%s\n" +
                        "收件人电话:%s\n" +
                        "收件人地址:%s\n" +
                        "订单总重量(kg):%.1f\n" +
                        "%s支付金额:%.1f\n",
                customer.getName(), customer.getPhone(),
                flight.getFlightNo(),
                orderNo,
                sdf.format(orderDate),
                deliveryInfo.getSenderName(),
                deliveryInfo.getSenderPhone(),
                deliveryInfo.getSenderAddress(),
                deliveryInfo.getRecipientName(),
                deliveryInfo.getRecipientPhone(),
                deliveryInfo.getRecipientAddress(),
                getTotalChargeableWeight(),
                paymentName,
                paymentMethod.pay(calculateTotalFreight())
        );
    }

    public String generateCargoDetailReport() {
        StringBuilder sb = new StringBuilder("货物明细如下:\n-----------------------------------------\n");
        sb.append("明细编号\t货物名称\t计费重量\t计费费率\t应交运费\n");
        for (int i = 0; i < items.size(); i++) {
            OrderItem item = items.get(i);
            sb.append(String.format(
                    "%d\t%s\t%.1f\t%.1f\t%.1f\n",
                    i + 1,
                    item.getCargo().getName(),
                    item.getChargeableWeight(),
                    item.getRate(),
                    item.calculateInitailFreight()
            ));
        }
        return sb.toString();
    }
}

class OrderItem {
    private CargoInfo cargo;
    private FreightCalculator calculator;

    public OrderItem(CargoInfo cargo, String customerType) {
        this.cargo = cargo;
        this.calculator = new FreightCalculator();
        double weight = cargo.getChargeableWeight();
        String type = cargo.getType();
        if (type.equals("Normal")) {
            setNormalRate(weight);
        } else if (type.equals("Dangerous")) {
            setDangerousRate(weight);
        } else if (type.equals("Expedite")) {
            setExpediteRate(weight);
        }
    }

    private void setNormalRate(double weight) {
        if (weight < 20) calculator.setRateStrategy(new RateStrategy1());
        else if (weight < 50) calculator.setRateStrategy(new RateStrategy2());
        else if (weight < 100) calculator.setRateStrategy(new RateStrategy3());
        else calculator.setRateStrategy(new RateStrategy4());
    }

    private void setDangerousRate(double weight) {
        if (weight < 20) calculator.setRateStrategy(new RateStrategy5());
        else if (weight < 50) calculator.setRateStrategy(new RateStrategy6());
        else if (weight < 100) calculator.setRateStrategy(new RateStrategy7());
        else calculator.setRateStrategy(new RateStrategy8());
    }

    private void setExpediteRate(double weight) {
        if (weight < 20) calculator.setRateStrategy(new RateStrategy9());
        else if (weight < 50) calculator.setRateStrategy(new RateStrategy10());
        else if (weight < 100) calculator.setRateStrategy(new RateStrategy11());
        else calculator.setRateStrategy(new RateStrategy12());
    }

    public double getChargeableWeight() {
        return cargo.getChargeableWeight();
    }

    public double getRate() {
        return calculator.getRateStrategy().getRate(getChargeableWeight());
    }

    public double calculateFreight(String customerType) {
        return calculator.calculateBaseFreight(getChargeableWeight(), customerType);
    }

    public double calculateInitailFreight() {
        return calculator.initialBaseFreight(getChargeableWeight());
    }

    public CargoInfo getCargo() {
        return cargo;
    }
}

class FreightCalculator {
    private RateStrategy rateStrategy;

    public void setRateStrategy(RateStrategy rateStrategy) {
        this.rateStrategy = rateStrategy;
    }

    public RateStrategy getRateStrategy() {
        return rateStrategy;
    }

    public double initialBaseFreight(double chargeableWeight) {
        return chargeableWeight * rateStrategy.getRate(chargeableWeight);
    }
    
    public double calculateBaseFreight(double chargeableWeight, String customerType) {
        double freight = chargeableWeight * rateStrategy.getRate(chargeableWeight);
        return "Individual".equals(customerType) ? freight * 0.9 : freight * 0.8;
    }
}

class CargoInfo {
    private String name;
    private double width;   // cm
    private double length;  // cm
    private double height;  // cm
    private double grossWeight;// kg
    private String type;

    public CargoInfo(String name, double width, double length, double height, double grossWeight, String type) {
        this.name = name;
        this.width = width;
        this.length = length;
        this.height = height;
        this.grossWeight = grossWeight;
        this.type = type;
    }

    public double calculateVolumeWeight() {
        return (width * length * height) / 6000;
    }

    public double getChargeableWeight() {
        return Math.max(grossWeight, calculateVolumeWeight());
    }

    public String getName() {
        return name;
    }

    public String getType() {
        return type;
    }
}

class FlightInfo {
    private String flightNo;
    private String departureAirport;
    private String arrivalAirport;
    private Date flightDate;
    private double maxWeight;

    public FlightInfo(String flightNo, String departureAirport, String arrivalAirport, Date flightDate, double maxWeight) {
        this.flightNo = flightNo;
        this.departureAirport = departureAirport;
        this.arrivalAirport = arrivalAirport;
        this.flightDate = flightDate;
        this.maxWeight = maxWeight;
    }

    public String getFlightNo() {
        return flightNo;
    }

    public double getMaxWeight() {
        return maxWeight;
    }
}

class DeliveryInfo {
    private String senderName;
    private String senderPhone;
    private String senderAddress;
    private String recipientName;
    private String recipientPhone;
    private String recipientAddress;

    public DeliveryInfo(String senderName, String senderPhone, String senderAddress,
                        String recipientName, String recipientPhone, String recipientAddress) {
        this.senderName = senderName;
        this.senderPhone = senderPhone;
        this.senderAddress = senderAddress;
        this.recipientName = recipientName;
        this.recipientPhone = recipientPhone;
        this.recipientAddress = recipientAddress;
    }

    public String getSenderName() { return senderName; }
    public String getSenderPhone() { return senderPhone; }
    public String getSenderAddress() { return senderAddress; }
    public String getRecipientName() { return recipientName; }
    public String getRecipientPhone() { return recipientPhone; }
    public String getRecipientAddress() { return recipientAddress; }
}

class CustomerInfo {
    private String id;
    private String name;
    private String phone;
    private String address;
    private String type;

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

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

类图

代码分析

代码注释过少,因为第二次改动只改部分前面的注释好多没加进去

解释

  • 1.基本参数解读
    • 代码规模:行数 464,语句数 156(语句密度低,可能存在空行或简洁代码)。注释占比 2.4%(极低,需增强注释以提升可读性)。分支语句占比 10.3%(中等,存在条件判断逻辑),方法调用语句 45 条(模块间交互正常)。
    • 结构指标:类 / 接口数量 18(较多,代码模块化程度高),平均每类方法数 1.22(多数类为单方法类,如工具类或单一职责类)。平均每方法语句数 4.00(方法短小,符合单一职责原则,可维护性好)。
    • 复杂度指标:最复杂方法为 Main.main()(行号 8),作为程序入口,可能因初始化逻辑或嵌套导致复杂度高,需重点优化。
  • 2.图形分析
    • Kiviat 图(雷达图):展示注释占比、类方法数、方法平均语句数、最大复杂度 / 深度等指标。注释占比(2.4%)和方法平均语句数(4.00)在图中靠近中心,反映其 “短小少注” 的特点;最大复杂度 / 深度需结合代码检查(如main方法的嵌套逻辑)。
    • Block Histogram(直方图):深度(嵌套层次)分布显示,0-4 层语句占比高(尤其是 2-3 层),5+ 层极少,说明嵌套复杂度可控(无过深嵌套,如 9+ 层几乎为零),整体逻辑结构清晰。

心得

09作业和前面作业我是隔了一段时间再写的,以为只是写小的改动,但是由于忘了许多,中间还是出了不少差错的,比如类之间关系之间的改动老是搞混,果然良好的注释还是很重要的(老实了)

踩坑心得

题目集08

格式错误真的好搞人,调试了好久

题目集09

这里没有注意到输出的总金额和单项金额是不一样的(总金额是打折后的,单向金额没有),由于一开始方法就是固定加入了打折系数计算,纠结了好久,只能再后面单独写了不打折的方法输出,可能对整体有些冗余吧

改进建议

题目集08

  • 职责单一性优化:Order类承担了订单数据存储、运费计算、报告生成等多个职责
  • 策略模式优化:费率策略的选择逻辑(OrderItem构造函数中根据重量选择RateStrategy)与OrderItem强耦合,若新增策略需修改OrderItem代码,违反开闭原则
  • 注释补充:代码注释仅在方法前和少部分地方,需要提高代码注释量,提高代码可读性

题目集09

  • 代码复用与冗余:重复的重量区间判断setNormalRate、setDangerousRate等方法存在大量重复的重量判断逻辑,可提取为公共方法
  • 单一职责原则:拆分报告生成、支付计算等职责,避免类膨胀

总结

  • 面向对象设计的核心启示
    • 单一职责是设计的基石:每个类应仅有一个修改原因,避免 “大杂烩” 类,通过职责拆分提升代码可维护性。
    • 开闭原则指导扩展方向:新增功能时优先通过扩展(如新增策略类、工厂类)而非修改现有代码,确保系统弹性。
    • 依赖抽象而非具体:高层模块应依赖接口或抽象类,通过策略模式、工厂模式等解耦组件,降低类间耦合
    • 注释与命名是代码的 “说明书”:清晰的注释和语义化命名是未来加入团队协作的基础
  • 通过两次迭代作业,深刻体会到面向对象设计原则并非孤立应用,而是需要结合具体场景综合权衡。未来需在需求分析阶段提前规划类职责与交互,避免后期重构成本过高,同时通过单元测试验证设计的健壮性,确保系统符合 “高内聚、低耦合” 的核心目标
posted @ 2025-05-25 23:46  晚诉  阅读(10)  评论(0)    收藏  举报