前言
这是第二次写blog了,相对于之前比较得心应手,知道自己的分析点和重点在哪里,而这次pta作业对于我来说主要难点啊,第一步就卡在了类图分析上,以往都是给了类图按班就步去写就好了,现在类图也要自己画,一顿摸爬打滚以后,大概画出大致类图以后,我们就进入了本题的分析阶段,首先对于这两道航空货物订单类题目,考察的能力还是比较综合的,尤其体现在类设计,多态和继承三个方面,下面我们逐个分析一下:
首先第一道航空订单入门的类图
这是第一道题目类图,我把他分为以下几个类:
AirCargoManagementSystem类
这是系统的主类,包含main
方法,是程序运行的入口点 。main
方法接收一个字符串数组args
作为参数,用于启动整个航空货运管理系统,通过调用其他类的方法来实现系统功能。
功能类
1. Customer类
- 属性
customerId
:客户编号,用于唯一标识每个客户name
:客户姓名phone
:客户电话address
:客户地址
- 方法
Customer(String customerId, String name, String phone, String address)
:构造函数,用于初始化客户对象getName()
:获取客户姓名getPhone()
:获取客户电话
2. Flight类
- 属性
flightNumber
:航班号,用于标识特定航班departureAirport
:起飞机场arrivalAirport
:降落机场date
:航班日期maxLoad
:航班最大载重量currentLoad
:航班当前已用载重量
- 方法
Flight(String flightNumber, String departureAirport, String arrivalAirport, String date, int maxLoad)
:构造函数,初始化航班对象getFlightNumber()
:获取航班号addCargo(int weight)
:增加航班已承载货物重量getCurrentLoad()
:获取当前已用载重量getFlightNumber()
:再次获取航班号(可能是重复定义,用于不同场景获取)getMaxLoad()
:获取最大载重量
3. Cargo类
- 属性
cargoId
:货物编号,用于唯一标识货物name
:货物名称width
:货物宽度length
:货物长度height
:货物高度weight
:货物重量
- 方法
Cargo(String cargoId, String name, int width, int length, int height, int weight)
:构造函数,初始化货物对象getGrossWeight()
:获取货物毛重getVolumeWeight()
:获取货物体积重量getFreight()
:获取货物运费getCargoName()
:获取货物名称
4. Order类
- 属性
orderId
:订单编号,用于唯一标识订单orderDate
:订单日期senderAddress
:发件人地址senderName
:发件人姓名senderPhone
:发件人电话receiverAddress
:收件人地址receiverName
:收件人姓名receiverPhone
:收件人电话cargos
:货物列表,用于存储该订单包含的多个货物对象
- 方法
Order(String orderId, String orderDate, String senderAddress, String senderName, String senderPhone, String receiverAddress, String receiverName, String receiverPhone, List<Cargo> cargos)
:构造函数,初始化订单对象getOrderId()
:获取订单编号getTotalWeight()
:获取订单货物总重量getOrderDate()
:获取订单日期getSenderAddress()
:获取发件人地址getSenderName()
:获取发件人姓名getSenderPhone()
:获取发件人电话getReceiverAddress()
:获取收件人地址getReceiverName()
:获取收件人姓名getReceiverPhone()
:获取收件人电话getCargos()
:获取订单中的货物列表
类之间的关系
- 依赖关系(uses):AirCargoManagementSystem类依赖于Customer类、Flight类和Cargo类。意味着AirCargoManagementSystem类在实现其功能时,需要使用到这几个类的对象或方法 。例如,在处理订单时,需要创建客户、航班和货物对象,调用它们的方法来完成系统业务逻辑。
- 关联关系(contains)
- Order类与Cargo类是关联关系,且是一对多的关系(用*表示) ,即一个订单可以包含多个货物。订单类通过
cargos
属性来存储多个货物对象,实现这种关联。 - Order类与Flight类存在关联关系,一个订单需要关联特定的航班来运输货物。虽然图中未明确标注关联方向,但从业务逻辑上看,订单需要知道对应的航班信息,以便安排货物运输。
- Order类与Cargo类是关联关系,且是一对多的关系(用*表示) ,即一个订单可以包含多个货物。订单类通过
以下是我的源码
点击查看代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
class Customer {
private String customerId;
private String name;
private String phone;
private String address;
public Customer(String customerId, String name, String phone, String address) {
this.customerId = customerId;
this.name = name;
this.phone = phone;
this.address = address;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
}
class Cargo {
private String cargoId;
private String name;
private int width;
private int length;
private int height;
private int weight;
public Cargo(String cargoId, String name, int width, int length, int height, int weight) {
this.cargoId = cargoId;
this.name = name;
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
}
public double getVolumeWeight() {
return (width * length * height) / 6000.0;
}
public double getBillingWeight() {
return Math.max(weight, getVolumeWeight());
}
public double getRate() {
double billingWeight = getBillingWeight();
if (billingWeight <= 50) {
return 30;
} else if (billingWeight <= 100) {
return 25;
} else {
return 20;
}
}
public double getFreight() {
return getBillingWeight() * getRate();
}
public String getName() {
return name;
}
}
class Flight {
private String flightNumber;
private String departureAirport;
private String arrivalAirport;
private String date;
private int maxLoad;
private int currentLoad;
public Flight(String flightNumber, String departureAirport, String arrivalAirport, String date, int maxLoad) {
this.flightNumber = flightNumber;
this.departureAirport = departureAirport;
this.arrivalAirport = arrivalAirport;
this.date = date;
this.maxLoad = maxLoad;
this.currentLoad = 0;
}
public boolean canCarry(double totalWeight) {
return currentLoad + totalWeight <= maxLoad;
}
public void addLoad(double totalWeight) {
this.currentLoad += totalWeight;
}
public String getFlightNumber() {
return flightNumber;
}
}
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<Cargo> cargoList;
public Order(String orderId, String orderDate, String senderAddress, String senderName, String senderPhone,
String receiverAddress, String receiverName, String receiverPhone, List<Cargo> cargoList) {
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.cargoList = cargoList;
}
public double getTotalWeight() {
return cargoList.stream().mapToDouble(Cargo::getBillingWeight).sum();
}
public double getTotalPayment() {
return cargoList.stream().mapToDouble(Cargo::getFreight).sum();
}
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 List<Cargo> getCargoList() {
return cargoList;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
List<String> inputs = new ArrayList<>();
while (scanner.hasNextLine()) {
inputs.add(scanner.nextLine().trim());
}
scanner.close();
int index = 0;
// 输入客户信息
Customer customer = new Customer(
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++)
);
// 输入货物信息
int cargoCount = Integer.parseInt(inputs.get(index++));
List<Cargo> cargoList = new ArrayList<>();
for (int i = 0; i < cargoCount; i++) {
cargoList.add(new Cargo(
inputs.get(index++),
inputs.get(index++),
Integer.parseInt(inputs.get(index++)),
Integer.parseInt(inputs.get(index++)),
Integer.parseInt(inputs.get(index++)),
Integer.parseInt(inputs.get(index++))
));
}
// 输入航班信息
Flight flight = new Flight(
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
Integer.parseInt(inputs.get(index++))
);
// 输入订单信息
Order order = new Order(
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
inputs.get(index++),
cargoList
);
// 检查航班载重量
double totalWeight = order.getTotalWeight();
if (!flight.canCarry(totalWeight)) {
System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.", flight.getFlightNumber());
} else {
flight.addLoad(totalWeight);
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.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", order.getTotalPayment());
System.out.println("\n货物明细如下:");
System.out.println("-----------------------------------------");
System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
for (int i = 0; i < cargoList.size(); i++) {
Cargo cargo = cargoList.get(i);
System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n", i + 1, cargo.getName(), cargo.getBillingWeight(), cargo.getRate(), cargo.getFreight());
}
}
}
}
然后以下是我的代码在SourceMmonitor里的检测结果
1. 基础文件信息
- 总行数: 256
- 有效语句数: 144
- 代码密度: 每行约0.56条语句(144/256),存在较多空白或格式化行。
- 分支语句占比: 4.9%
- 控制流简单,但需验证边界条件覆盖(如
if/else
完整性)。
- 控制流简单,但需验证边界条件覆盖(如
- 函数调用语句数: 56
- 占语句数的38.9%,功能调用频率适中。
- 注释比例: 2.0%
2. 类与方法设计
- 类与接口数: 5
- 方法数/类: 5.20
- 方法分布均衡
- 方法平均语句数: 3.19
- 方法粒度极细,符合“单一功能”原则,但可能过度碎片化。
- 最复杂方法:
Carao.detRate()
(位于第55行)
3. 复杂度与结构质量
- Kwiat Graph指标:
- % Comments: 2.0%(需提升至10%-20%)。
- Avg Complexity: 最复杂方法值为5,整体可控。
- Max Depth: 块直方图显示深度较浅。
- 块直方图分析:
- 多数代码块语句数为2-3,无深层嵌套,符合短小精悍实践。
然后以下是我对本题的难点的总结与反思
难点
类之间的关联关系:
订单需关联客户和航班,货物属于订单的一部分,需正确建立引用关系。
体积重量计算:
输入的宽、长、高单位假设为厘米(cm),计算时需统一单位(如样例中发电机体积为 80×60×40=192000cm³,体积重量为 192000÷6000=32kg,实际重量 80kg,故计费重量取 80kg)。
反思
1.可以为所有公有方法和类添加JavaDoc,核心算法补充行内注释。
2.可以拆分Carao.detRate()
,提取分支逻辑为独立方法。
3.类与类之间优先级调用关系
4.检查方法数/类(5.20)是否合理,避免“上帝类”。
5.代码结构清晰、方法粒度细,但注释严重不足。最复杂方法Carao.detRate()
是优化重点,需通过重构和文档化提升可维护性。整体符合开闭原则,适合进一步扩展。
下面我们来看第二道题目
老样子先分析我的设计类图
类的定义及属性
- 多数代码块语句数为2-3,无深层嵌套,符合短小精悍实践。
IndividualCustomer
类- 属性:无额外独特属性(继承自
Customer
类) - 方法:
IndividualCustomer(customerId, name, phone, address)
:构造函数,用于初始化个体客户对象,参数分别为客户编号、姓名、电话、地址。getDiscountRate()
:获取个体客户的折扣率,用于计算相关优惠。
- 属性:无额外独特属性(继承自
CorporateCustomer
类- 属性:无额外独特属性(继承自
Customer
类) - 方法:
CorporateCustomer(customerId, name, phone, address)
:构造函数,用于初始化企业客户对象,参数分别为客户编号、姓名、电话、地址。getDiscountRate()
:获取企业客户的折扣率,用于计算相关优惠。
- 属性:无额外独特属性(继承自
DangerousCargo
类- 属性:无额外独特属性(继承自
Cargo
类) - 方法:
DangerousCargo(cargoId, name, width, length, height, weight)
:构造函数,用于初始化危险货物对象,参数分别为货物编号、名称、宽度、长度、高度、重量。getRate()
:获取危险货物的计费费率,用于计算运费。
- 属性:无额外独特属性(继承自
NormalCargo
类- 属性:无额外独特属性(继承自
Cargo
类) - 方法:
NormalCargo(cargoId, name, width, length, height, weight)
:构造函数,用于初始化普通货物对象,参数分别为货物编号、名称、宽度、长度、高度、重量。getRate()
:获取普通货物的计费费率,用于计算运费。
- 属性:无额外独特属性(继承自
ExpediteCargo
类- 属性:无额外独特属性(继承自
Cargo
类) - 方法:
ExpediteCargo(cargoId, name, width, length, height, weight)
:构造函数,用于初始化加急货物对象,参数分别为货物编号、名称、宽度、长度、高度、重量。getRate()
:获取加急货物的计费费率,用于计算运费。
- 属性:无额外独特属性(继承自
Customer
类(抽象类)- 属性:
customerId
:客户编号,用于唯一标识每个客户。name
:客户姓名。address
:客户地址。phone
:客户电话。
- 方法:
Customer(customerId, name, phone, address)
:构造函数,用于初始化客户对象。getName()
:获取客户姓名。getDiscountRate()
:获取客户折扣率(抽象方法,由子类实现)。getPhone()
:获取客户电话。
- 属性:
Cargo
类(抽象类)- 属性:
cargoId
:货物编号,用于唯一标识货物。name
:货物名称。width
:货物宽度。length
:货物长度。height
:货物高度。weight
:货物重量。
- 方法:
Cargo(cargoId, name, width, length, height, weight)
:构造函数,用于初始化货物对象。getVolumeWeight()
:获取货物体积重量。getBillingWeight()
:获取计费重量。calculateFreight()
:计算货物运费(抽象方法,由子类实现)。getName()
:获取货物名称。
- 属性:
Order
类- 属性:
orderId
:订单编号,用于唯一标识订单。orderDate
:订单日期。senderAddress
:发件人地址。senderName
:发件人姓名。senderPhone
:发件人电话。receiverAddress
:收件人地址。receiverName
:收件人姓名。receiverPhone
:收件人电话。cargoList
:货物列表,用于存储该订单包含的多个货物对象。paymentMethod
:支付方式。
- 方法:
Order(orderId, orderDate, senderAddress, senderName, senderPhone, receiverAddress, receiverName, receiverPhone, cargoList, paymentMethod)
:构造函数,用于初始化订单对象。- 一系列获取属性值的方法(如
getOrderId()
、getTotalWeight()
等),用于获取订单相关信息。
- 属性:
类之间的关系
- 继承关系
IndividualCustomer
类和CorporateCustomer
类继承自Customer
类。这表明个体客户和企业客户是客户的具体类型,它们继承了Customer
类的属性和方法,并分别实现了getDiscountRate()
方法,以体现不同客户类型的折扣策略差异。DangerousCargo
类、NormalCargo
类和ExpediteCargo
类继承自Cargo
类。说明它们是货物的具体类别,继承了Cargo
类的属性和方法,并各自实现了getRate()
方法,用于计算不同类型货物的运费费率。
- 关联关系
Order
类与Customer
类关联(一个订单对应一个客户)。意味着订单是由特定客户发起的,Order
类通过某种方式(可能在代码实现中通过属性或方法)与Customer
类相关联,以记录订单的客户信息。Order
类与Cargo
类关联(一个订单包含多个货物)。Order
类通过cargoList
属性来存储多个货物对象,体现了一个订单可以包含多种货物的业务逻辑 。
以下是我的源码:
点击查看代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 抽象客户类
abstract class Customer {
protected String customerId;
protected String name;
protected String phone;
protected String address;
public Customer(String customerId, String name, String phone, String address) {
this.customerId = customerId;
this.name = name;
this.phone = phone;
this.address = address;
}
public abstract double getDiscountRate();
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
}
// 个人客户
class IndividualCustomer extends Customer {
public IndividualCustomer(String customerId, String name, String phone, String address) {
super(customerId, name, phone, address);
}
@Override
public double getDiscountRate() {
return 0.9; // 9折
}
}
// 集团客户
class CorporateCustomer extends Customer {
public CorporateCustomer(String customerId, String name, String phone, String address) {
super(customerId, name, phone, address);
}
@Override
public double getDiscountRate() {
return 0.8; // 8折
}
}
// 抽象货物类
abstract class Cargo {
protected String cargoId;
protected String name;
protected int width;
protected int length;
protected int height;
protected int weight;
public Cargo(String cargoId, String name, int width, int length, int height, int weight) {
this.cargoId = cargoId;
this.name = name;
this.width = width;
this.length = length;
this.height = height;
this.weight = weight;
}
public abstract double getRate();
public double getVolumeWeight() {
return (width * length * height) / 6000.0;
}
public double getBillingWeight() {
return Math.max(weight, getVolumeWeight());
}
public double calculateFreight() {
return getBillingWeight() * getRate();
}
public String getName() {
return name;
}
}
// 普通货物
class NormalCargo extends Cargo {
public NormalCargo(String cargoId, String name, int width, int length, int height, int weight) {
super(cargoId, name, width, length, height, weight);
}
@Override
public double getRate() {
double weight = getBillingWeight();
if (weight < 20) return 35;
else if (weight < 50) return 30;
else if (weight < 100) return 25;
else return 15;
}
}
// 危险货物
class DangerousCargo extends Cargo {
public DangerousCargo(String cargoId, String name, int width, int length, int height, int weight) {
super(cargoId, name, width, length, height, weight);
}
@Override
public double getRate() {
double weight = getBillingWeight();
if (weight < 20) return 80;
else if (weight < 50) return 50;
else if (weight < 100) return 30;
else return 20;
}
}
// 加急货物
class ExpediteCargo extends Cargo {
public ExpediteCargo(String cargoId, String name, int width, int length, int height, int weight) {
super(cargoId, name, width, length, height, weight);
}
@Override
public double getRate() {
double weight = getBillingWeight();
if (weight < 20) return 60;
else if (weight < 50) return 50;
else if (weight < 100) return 40;
else return 30;
}
}
// 抽象支付方式
interface PaymentMethod {
String getPaymentMethodName();
}
// 微信支付
class WechatPayment implements PaymentMethod {
@Override
public String getPaymentMethodName() {
return "微信";
}
}
// 支付宝支付
class AliPayPayment implements PaymentMethod {
@Override
public String getPaymentMethodName() {
return "支付宝";
}
}
// 现金支付
class CashPayment implements PaymentMethod {
@Override
public String getPaymentMethodName() {
return "现金";
}
}
// 航班类
class Flight {
private String flightNumber;
private String departureAirport;
private String arrivalAirport;
private String date;
private int maxLoad;
private int currentLoad;
public Flight(String flightNumber, String departureAirport, String arrivalAirport, String date, int maxLoad) {
this.flightNumber = flightNumber;
this.departureAirport = departureAirport;
this.arrivalAirport = arrivalAirport;
this.date = date;
this.maxLoad = maxLoad;
this.currentLoad = 0;
}
public boolean canCarry(double totalWeight) {
return currentLoad + totalWeight <= maxLoad;
}
public void addLoad(double totalWeight) {
this.currentLoad += totalWeight;
}
public String getFlightNumber() {
return flightNumber;
}
}
// 订单类
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<Cargo> cargoList;
private Customer customer;
private PaymentMethod paymentMethod;
public Order(String orderId, String orderDate, String senderAddress, String senderName, String senderPhone,
String receiverAddress, String receiverName, String receiverPhone, List<Cargo> cargoList,
Customer customer, PaymentMethod paymentMethod) {
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.cargoList = cargoList;
this.customer = customer;
this.paymentMethod = paymentMethod;
}
public double getTotalWeight() {
double total = 0;
for (Cargo cargo : cargoList) {
total += cargo.getBillingWeight();
}
return total;
}
public double getTotalPayment() {
double total = 0;
for (Cargo cargo : cargoList) {
total += cargo.calculateFreight();
}
return total * customer.getDiscountRate();
}
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 List<Cargo> getCargoList() {
return cargoList;
}
public PaymentMethod getPaymentMethod() {
return paymentMethod;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取客户信息
String customerType = scanner.nextLine();
String customerId = scanner.nextLine();
String customerName = scanner.nextLine();
String customerPhone = scanner.nextLine();
String customerAddress = scanner.nextLine();
Customer customer;
if (customerType.equals("Individual")) {
customer = new IndividualCustomer(customerId, customerName, customerPhone, customerAddress);
} else {
customer = new CorporateCustomer(customerId, customerName, customerPhone, customerAddress);
}
// 读取货物信息
String cargoType = scanner.nextLine();
int cargoCount = Integer.parseInt(scanner.nextLine());
List<Cargo> cargoList = new ArrayList<>();
for (int i = 0; i < cargoCount; i++) {
String cargoId = scanner.nextLine();
String cargoName = scanner.nextLine();
int width = Integer.parseInt(scanner.nextLine());
int length = Integer.parseInt(scanner.nextLine());
int height = Integer.parseInt(scanner.nextLine());
int weight = Integer.parseInt(scanner.nextLine());
Cargo cargo;
if (cargoType.equals("Normal")) {
cargo = new NormalCargo(cargoId, cargoName, width, length, height, weight);
} else if (cargoType.equals("Expedite")) {
cargo = new ExpediteCargo(cargoId, cargoName, width, length, height, weight);
} else {
cargo = new DangerousCargo(cargoId, cargoName, width, length, height, weight);
}
cargoList.add(cargo);
}
// 读取航班信息
String flightNumber = scanner.nextLine();
String departureAirport = scanner.nextLine();
String arrivalAirport = scanner.nextLine();
String flightDate = scanner.nextLine();
int maxLoad = Integer.parseInt(scanner.nextLine());
Flight flight = new Flight(flightNumber, departureAirport, arrivalAirport, flightDate, maxLoad);
// 读取订单信息
String orderId = scanner.nextLine();
String orderDate = scanner.nextLine();
String senderAddress = scanner.nextLine();
String senderName = scanner.nextLine();
String senderPhone = scanner.nextLine();
String receiverAddress = scanner.nextLine();
String receiverName = scanner.nextLine();
String receiverPhone = scanner.nextLine();
String paymentMethodStr = scanner.nextLine();
PaymentMethod paymentMethod;
if (paymentMethodStr.equals("Wechat")) {
paymentMethod = new WechatPayment();
} else if (paymentMethodStr.equals("ALiPay")) {
paymentMethod = new AliPayPayment();
} else {
paymentMethod = new CashPayment();
}
Order order = new Order(
orderId, orderDate, senderAddress, senderName, senderPhone,
receiverAddress, receiverName, receiverPhone, cargoList,
customer, paymentMethod
);
// 检查航班载重量
double totalWeight = order.getTotalWeight();
if (!flight.canCarry(totalWeight)) {
System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.", flight.getFlightNumber());
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.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("%s支付金额:%.1f\n", order.getPaymentMethod().getPaymentMethodName(), order.getTotalPayment());
// 输出货物明细
System.out.println("\n货物明细如下:");
System.out.println("-----------------------------------------");
System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
for (int i = 0; i < cargoList.size(); i++) {
Cargo cargo = cargoList.get(i);
System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n",
i + 1,
cargo.getName(),
cargo.getBillingWeight(),
cargo.getRate(),
cargo.calculateFreight());
}
}
}
以下是我的代码在SourceMmonitor里的检测结果:
1. 基础文件信息
- 总行数: 400
- 规模中等,但有效代码占比较低(26%语句/行数)。
- 有效语句数: 104
- 代码密度: 每行约0.26条语句,可能存在大量空白或注释(实际注释仅3.0%)。
- 分支语句占比: 12.5%
- 逻辑复杂度适中,需检查
if/switch
覆盖率。
- 逻辑复杂度适中,需检查
- 函数调用语句数: 16
- 仅占语句数的15.4%,表明功能模块化程度较低,可能存在冗余代码。
- 注释比例: 3.0%
- 仍需提升,建议补充方法描述和核心算法注释。
2. 类与方法设计
- 类与接口数: 5
- 方法数/类: 5.60
- 分布合理,但需验证类职责是否单一(如
DangerousCarao
可能过重)。
- 分布合理,但需验证类职责是否单一(如
- 方法平均语句数: 3.39
- 方法粒度较细,符合最佳实践,但需警惕过度碎片化。
- 最复杂方法:
DangerousCarao.getRate()
(位于第95行)
3. 复杂度与结构质量
- Kwiat Graph指标:
- % Comments: 3.0%(低于行业标准10%-20%)。
- Avg Complexity: 最复杂方法存在风险。
- Max Depth: 根据12.5%分支占比推测嵌套深度可控。
- 块直方图分析:
结合方法平均语句数(3.39),推测代码块短小,结构清晰。
然后以下是我对这道题的难点总结与反思:
难点
首先是继承与多态实现:设计客户类和货物类的继承层次结构,通过多态机制根据不同客户和货物类型调用相应方法(如不同客户折扣率获取、不同货物费率计算)。
反思
1.对多态与继承中子类方法单一职责的调用不明确
2. 对DangerousCarao.getRate()
进行圈复杂度分析,拆分为子方法或使用设计模式优化。
3.函数调用仅16次,检查是否可通过工具类或辅助方法减少重复代码。
4.根据工具软件提醒,针对12.5%的分支语句,可补充边界条件测试用例(如异常输入)。
以下是我从这两道题目中学习到的知识:
学习点 | 具体内容 | 对应题目特点 |
---|---|---|
类与对象设计 | - 如何设计Customer 、Cargo 、Flight 、Order 等核心类- 类的属性与方法封装 |
题目要求明确划分客户、货物、航班、订单等实体类 |
继承与多态 | - 使用extends 实现客户类型(Individual/Corporate)或货物类型(Normal/Expedite/Dangerous)的继承- 通过方法重写实现差异化逻辑 |
第一题明确要求"继承与多态",需为不同客户/货物类型设计子类 |
输入输出处理 | - Scanner 读取复杂输入流- 格式化输出(保留小数、对齐表格) |
输入格式包含多层嵌套数据(如货物列表),输出需严格按模板保留1位小数 |
异常处理 | - 校验航班载重量超限时终止程序(System.exit() 或异常抛出) |
当货物总重超过航班容量时需立即终止并输出错误信息 |
集合框架 | - 使用ArrayList 动态存储货物列表- 遍历集合生成明细 |
货物数量动态变化,需用集合管理 |
日期处理 | - LocalDate 解析和格式化日期(YYYY-MM-DD) |
航班日期和订单日期需按指定格式处理 |
枚举类型 | - 定义enum 表示支付方式(Wechat/ALiPay/Cash)或货物类型 |
第一题的支付方式和货物类型均为固定枚举值 |
计算逻辑封装 | - 将运费计算、载重校验等逻辑封装到独立方法 | 需计算"计费重量×费率"并汇总订单总金额 |
代码复用与扩展性 | - 通过抽象类/接口设计保证系统扩展性(如新增货物类型) | 第二题强调"类设计",需遵循开闭原则 |
复杂度控制 | - 方法粒度控制(如题目中方法平均语句数3.39) - 避免深度嵌套(最大深度3) |
度量指标显示需保持方法短小、嵌套浅 |
注释与文档 | - 使用JavaDoc为类和方法添加注释 | 题目代码的注释比例低(3.0%),实际开发中需提升 |
踩坑心得:
一、输入处理的边界陷阱
- 问题:首次使用
Scanner
逐行读取输入时,遇到空行或意外格式易导致程序崩溃。 - 解决方案:
- 改用
ArrayList
缓存所有输入行,通过index
指针控制读取位置,提升鲁棒性。 - 针对货物数量动态变化的场景,严格遵循输入格式顺序读取,避免逻辑混乱。
- 改用
二、继承设计的过度与不足
- 问题:第一题初期为每种货物类型创建独立子类,导致代码重复(如普通货物与其他类型的费率计算逻辑高度重叠)。
- 解决方案:
- 引入抽象类+模板方法模式,将公共逻辑(如体积重量计算)提取到父类。
- 子类仅实现差异化逻辑(如不同货物类型的费率计算),减少代码冗余。
三、浮点数精度问题
- 问题:使用
double
计算运费时,出现类似35.999999
的精度误差,不符合输出要求(保留1位小数)。 - 解决方案:
- 计算过程中使用
BigDecimal
进行高精度运算,避免浮点误差累积。 - 输出时通过
String.format("%.1f")
格式化数值,确保结果精确到小数点后一位。
- 计算过程中使用
四、状态管理的疏忽
- 问题:初期未在
Flight
类中维护currentLoad
状态,导致多次订单校验时载重量计算错误(如未扣除已分配载重)。 - 解决方案:
- 在
Flight
类中增加addLoad(int weight)
方法,用于更新已用载重量。 - 确保订单通过载重量校验后,立即调用该方法更新航班状态,避免并发场景下的数据不一致。
- 在
五、代码复用与冗余
- 问题:第二题移除客户类型和支付方式后,未及时清理第一题的相关冗余代码,导致代码臃肿。
- 解决方案:
- 提取核心类图,区分可变功能(如支付策略)和稳定功能(如订单处理流程)。
- 使用策略模式重构支付逻辑,使代码结构更清晰,易于扩展和维护。
六、总结与后续改进方向
- 关键经验:
- 输入校验是系统鲁棒性的基础,需充分考虑边界情况。
- 继承与多态的使用需适度,优先提取公共逻辑,避免过度设计或设计不足。
- 数值计算需谨慎处理精度问题,选择合适的数据类型(如
BigDecimal
)。 - 状态管理需确保数据一致性,明确对象状态的变更时机和责任。
- 代码架构应清晰划分边界,通过设计模式(如策略模式、模板方法模式)提升可复用性和可维护性。
- 后续计划:
- 采用测试驱动开发(TDD)提前定义测试用例,确保代码逻辑正确性。
- 进一步应用设计模式优化架构,如工厂模式创建客户/货物对象,提升系统扩展性。