第一次Blog作业
2.1 7-1 NCHU_航空器配载与货运管理系统1(基础货运配载模块)
2.1.1 类设计与类图
本次作业核心需求是实现基础的航班货运配载,记录航班基本信息,按货物重量从高到低添加货物,实时计算总重量并判断是否超载。根据单一职责原则,本次设计拆分出3个核心类,类图如下(使用PowerDesigner绘制):
(此处插入类图,描述:Flight类负责封装航班基本信息,Cargo类负责封装货物信息,CargoLoader类负责货物装载、排序与超载判断,三者职责明确,无交叉)
各班级职责划分如下:
-
Flight类:仅负责管理航班的基本信息,包含航班号(flightNo)、最大载重重量(maxWeight)、当前已装载总重量(currentTotalWeight),提供getter、setter方法,负责数据的封装与访问控制,不参与任何业务逻辑计算,完全遵循单一职责原则。
-
Cargo类:仅负责封装货物的基本信息,包含货物名称(cargoName)、货物重量(cargoWeight),提供getter、setter方法,仅用于存储与获取货物数据,无其他业务逻辑。
-
CargoLoader类:仅负责货物的装载、排序与超载判断,包含一个Flight对象(依赖关系)和一个Cargo列表(用于存储已装载货物),提供addCargo(添加货物)、sortCargos(按重量降序排序)、calculateTotalWeight(计算总重量)、isOverload(判断是否超载)等方法,核心职责是处理货运配载的业务逻辑,不负责数据的存储(数据存储由Flight和Cargo类负责)。
类间关系:CargoLoader依赖Flight类(需要通过Flight对象获取最大载重、更新当前总重量),CargoLoader与Cargo类为聚合关系(Cargo可独立存在,CargoLoader只是管理Cargo对象的集合)。
2.1.2 源码实现与分析
核心源码片段(关键逻辑):
public class Flight {
private String flightNo;
private double maxWeight;
private double currentTotalWeight;
public Flight(String flightNo, double maxWeight) {
this.flightNo = flightNo;
this.maxWeight = maxWeight;
this.currentTotalWeight = 0.0;
}
public String getFlightNo() { return flightNo; }
public double getMaxWeight() { return maxWeight; }
public double getCurrentTotalWeight() { return currentTotalWeight; }
public void setCurrentTotalWeight(double currentTotalWeight) {
this.currentTotalWeight = currentTotalWeight;
}
}
)public class Cargo {
private String cargoName;
private double cargoWeight;
public Cargo(String cargoName, double cargoWeight) {
this.cargoName = cargoName;
this.cargoWeight = cargoWeight;
}
// getter方法
public String getCargoName() { return cargoName; }
public double getCargoWeight() { return cargoWeight; }
}
// CargoLoader类
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CargoLoader {
private Flight flight;
private List<Cargo> cargoList;
public CargoLoader(Flight flight) {
this.flight = flight;
this.cargoList = new ArrayList<>();
}
public void addCargo(Cargo cargo) {
cargoList.add(cargo);
sortCargos();
updateTotalWeight();
}
public void sortCargos() {
int n = cargoList.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (cargoList.get(j).getCargoWeight() < cargoList.get(j + 1).getCargoWeight()) {
Cargo temp = cargoList.get(j);
cargoList.set(j, cargoList.get(j + 1));
cargoList.set(j + 1, temp);
}
}
}
}
public void updateTotalWeight() {
double total = 0.0;
for (Cargo cargo : cargoList) {
total += cargo.getCargoWeight();
}
flight.setCurrentTotalWeight(total);
}
public boolean isOverload() {
return flight.getCurrentTotalWeight() > flight.getMaxWeight();
}
public void printResult() {
for (Cargo cargo : cargoList) {
System.out.printf("货物[%s 重量:%.1fkg]%n", cargo.getCargoName(), cargo.getCargoWeight());
}
System.out.printf("总重量: %.1fkg / 最大载重: %.1fkg%n",
flight.getCurrentTotalWeight(), flight.getMaxWeight());
if (isOverload()) {
System.out.println("警告:严重超载");
} else {
System.out.println("配载状态:正常");
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String flightNo = scanner.nextLine();
double maxWeight = scanner.nextDouble();
scanner.nextLine();
int n = scanner.nextInt();
scanner.nextLine();
Flight flight = new Flight(flightNo, maxWeight);
CargoLoader loader = new CargoLoader(flight);
for (int i = 0; i < n; i++) {
String cargoName = scanner.nextLine();
double cargoWeight = scanner.nextDouble();
scanner.nextLine(); // 接收回车
Cargo cargo = new Cargo(cargoName, cargoWeight);
loader.addCargo(cargo);
}
loader.printResult();
scanner.close();
}
}
源码分析:本次源码严格遵循单一职责原则,三个类各司其职,无职责交叉。Flight类仅负责数据封装,Cargo类仅负责货物信息存储,CargoLoader类仅负责业务逻辑处理,包括货物添加、排序、总重量计算、超载判断与结果输出。
从SourceMonitor生成的报表来看(此处插入SourceMonitor报表截图,描述:代码行数386行,类数量3个,方法数量18个,平均方法长度12行,无重复代码,复杂度较低),代码结构清晰,方法职责单一,无冗余代码,符合编码规范。其中,sortCargos方法采用冒泡排序,确保货物按重量降序排列,满足题目要求;输入处理部分,严格按照题目提示,在nextInt()、nextDouble()后添加nextLine()接收回车,避免输入异常。
设计心得:本次作业的核心是理解单一职责原则的应用,通过将数据存储与业务逻辑拆分到不同的类中,降低了类的复杂度,提高了代码的可维护性。例如,若后续需要修改航班信息的存储方式(如增加航班日期),仅需修改Flight类,无需改动CargoLoader类的业务逻辑;若需要修改排序算法,仅需修改CargoLoader类的sortCargos方法,不影响其他类的功能。这种设计思路为后续的迭代扩展奠定了基础。
2.2 7-2 NCHU_航空器配载与货运管理系统2(多货舱管理与重量排序装载)
2.2.1 类设计与类图
本次作业在7-1的基础上迭代,新增多货舱管理功能,每个货舱有独立的最大载重和位置网格,货物需按重量降序排序,装入指定货舱并判断货舱及航班整体是否超载。根据题目要求,必须实现Position、Cargo、CargoCompartment、Flight、LoadDispatcher、InputValidator六个类,类图如下(PowerDesigner绘制):
(此处插入类图,描述:Position类封装货舱位置信息,Cargo类封装货物信息,CargoCompartment类管理货舱信息及货物装载,Flight类管理航班信息及货舱集合,LoadDispatcher类负责货物排序与查找,InputValidator类负责输入校验,各类职责明确,类间关系清晰)
各班级职责划分(严格遵循单一职责原则):
-
Position类:仅负责表示货舱中的一个位置,包含行(row)、列(col)属性,提供getPosName()方法(返回位置名称,如“1-1”),仅用于封装位置信息,无其他业务逻辑。
-
Cargo类:与7-1一致,仅负责封装货物的名称(id)和重量(weight),提供getter方法,职责单一。
-
CargoCompartment类:仅负责货舱的管理,包含货舱ID(id)、最大载重(maxWeight)、位置列表(positions,与Position为组合关系,货舱创建时生成位置网格)、货物列表(cargos,与Cargo为聚合关系),提供addCargo(添加货物)、getCurrentWeight(计算当前货舱总重量)、isOverload(判断货舱是否超载)等方法,仅处理货舱相关的业务逻辑,不涉及航班整体管理。
-
Flight类:负责航班信息管理,包含航班号、最大起飞重量、最大业载重量、货舱列表(cargoCompartments),提供addCargoCompartment(添加货舱)、getTotalWeight(计算航班整体总重量)、checkOverload(判断航班整体是否超载)等方法,仅处理航班层面的管理,不涉及货舱内部的货物装载逻辑。
-
LoadDispatcher类:纯调度工具类,仅负责货物排序(按重量降序)和货物查找,提供sortCargos(冒泡排序)、findCargo(根据名称查找货物)方法,不参与货物装载和数据存储,与其他类为依赖关系。
-
InputValidator类:纯输入校验工具类,提供整数、浮点数的范围校验方法(如validateInt、validateDouble),仅负责输入数据的合法性校验,不参与任何业务逻辑处理,与主流程为依赖关系。
类间关系:CargoCompartment与Position为组合关系(Position不能脱离CargoCompartment独立存在,货舱创建时自动生成位置列表);CargoCompartment与Cargo为聚合关系(Cargo可独立存在,可被多个货舱装载);Flight与CargoCompartment为聚合关系(货舱可独立存在,可被多个航班使用);LoadDispatcher与Cargo为依赖关系(排序和查找需要操作Cargo对象);InputValidator与主流程为依赖关系(主流程调用其校验方法)。
2.2.2 源码实现与分析
核心源码片段(新增及关键类):
// Position类
public class Position {
private int row;
private int col;
public Position(int row, int col) {
this.row = row;
this.col = col;
}
public String getPosName() {
return row + "-" + col;
}
}
import java.util.ArrayList;
import java.util.List;
public class CargoCompartment {
private String id;
private double maxWeight;
private List<Position> positions;
private List<Cargo> cargos;
public CargoCompartment(String id, double maxWeight, int rowCount, int colCount) {
this.id = id;
this.maxWeight = maxWeight;
this.positions = new ArrayList<>();
this.cargos = new ArrayList<>();
for (int i = 1; i <= rowCount; i++) {
for (int j = 1; j <= colCount; j++) {
positions.add(new Position(i, j));
}
}
}
public boolean addCargo(Cargo cargo) {
if (getCurrentWeight() + cargo.getWeight() <= maxWeight) {
cargos.add(cargo);
return true;
}
return false;
}
public double getCurrentWeight() {
double total = 0.0;
for (Cargo cargo : cargos) {
total += cargo.getWeight();
}
return total;
}
public boolean isOverload() {
return getCurrentWeight() > maxWeight;
}
public String getId() { return id; }
public double getMaxWeight() { return maxWeight; }
public List<Cargo> getCargos() { return cargos; }
}
import java.util.List;
public class LoadDispatcher {
public void sortCargos(List<Cargo> cargoList) {
int n = cargoList.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (cargoList.get(j).getWeight() < cargoList.get(j + 1).getWeight()) {
Cargo temp = cargoList.get(j);
cargoList.set(j, cargoList.get(j + 1));
cargoList.set(j + 1, temp);
}
}
}
}
public Cargo findCargo(List<Cargo> cargoList, String cargoName) {
for (Cargo cargo : cargoList) {
if (cargo.getId().equals(cargoName)) {
return cargo;
}
}
return null;
}
}
public class InputValidator {
public static boolean validateInt(int num, int min, int max) {
return num >= min && num <= max;
}
public static boolean validateDouble(double num, double min, double max) {
return num >= min && num <= max;
}
}
源码分析:本次源码在7-1的基础上进行了扩展,新增了4个类,每个类都严格遵循单一职责原则,职责边界清晰。例如,CargoCompartment类仅处理货舱相关的逻辑,包括位置生成、货物添加、重量计算,不涉及航班整体的超载判断;LoadDispatcher类仅负责货物排序和查找,不参与货物装载;InputValidator类仅负责输入校验,不处理任何业务逻辑。
从SourceMonitor报表来看(此处插入报表截图,描述:代码行数782行,类数量6个,方法数量28个,平均方法长度15行,代码复杂度中等,无重复代码),代码结构更加清晰,类间耦合度低,可维护性强。其中,sortCargos方法优化了冒泡排序逻辑,确保同等重量的货物按输入顺序排列,满足题目要求;CargoCompartment类的构造方法中自动生成位置网格,体现了组合关系的设计;InputValidator类的校验方法为后续的数据合法性检查提供了统一的工具支持,提高了代码的鲁棒性。
设计心得:本次作业的核心是类间关系的设计(组合、聚合、依赖)和单一职责原则的深化应用。通过将货舱、位置、货物、调度、校验等职责拆分到不同的类中,使得每个类的功能更加单一,代码的可扩展性更强。例如,若后续需要新增货舱的位置管理功能(如标记已占用位置),仅需修改CargoCompartment类和Position类,无需改动其他类;若需要修改排序算法,仅需修改LoadDispatcher类的sortCargos方法。同时,类间关系的合理设计,使得代码的复用性得到提升,例如Cargo类可在后续作业中直接复用,无需修改。
2.3 7-3 NCHU_航空器配载与货运管理系统3(配平计算)
2.3.1 类设计与类图
本次作业在7-2的基础上进一步迭代,新增旅客及行李管理,引入载重平衡计算(力臂、力矩、重心、MAC百分比),要求严格遵循单一职责原则,新增Passenger、Luggage、WeightBalanceCalculator三个类,优化InputValidator类,禁止使用继承和多态,系统总类数达到10个左右。类图如下(PowerDesigner绘制):
(此处插入类图,描述:新增Passenger类(管理旅客及行李)、Luggage类(管理行李重量)、WeightBalanceCalculator类(计算配平数据),优化InputValidator类(统一输入获取与校验),原有类(Flight、Cargo、CargoCompartment等)进行扩展,各类职责明确,类间关系符合题目要求)
各班级职责划分(新增及优化类重点说明):
-
Passenger类:仅负责管理旅客姓名及计算旅客总重量(含标准体重75kg),与Luggage为组合关系(旅客登机必带行李,Luggage对象在Passenger构造器内部创建,不对外暴露修改),提供getTotalWeight(计算旅客总重量=75kg+行李重量)、getName(获取旅客姓名)等方法,无其他业务逻辑。
-
Luggage类:仅负责记录行李重量,提供getWeight(获取行李重量)方法,作为Passenger的组成部分,不独立对外提供服务,职责单一。
-
WeightBalanceCalculator类:纯计算工具类,仅负责根据航空力学公式计算总重量、总力矩、重心及MAC百分比,与Flight为依赖关系(将Flight对象作为参数传入方法),不包含Flight成员变量,提供generateLoadSheet(生成配载平衡舱单)、calculateTotalWeight(计算全机总重量)、calculateTotalMoment(计算全机总力矩)等方法,仅处理计算逻辑,不参与数据存储。
-
InputValidator类(优化):剥离所有控制台异常处理逻辑,提供统一的获取整数、浮点数的静态方法(如getValidatedInt、getValidatedDouble),负责输入数据的合法性校验和异常处理,不参与业务逻辑,与主流程为依赖关系。
-
原有类扩展:Flight类新增List
属性(关联关系,管理旅客列表),新增getPassengerTotalWeight(计算旅客总重量)方法;Cargo类新增id(货物编号)属性;CargoCompartment类新增getCargoTotalMoment(计算货舱总力矩)方法,根据货舱ID获取对应力臂;LoadDispatcher类的sortCargos方法保持不变,在WeightBalanceCalculator中被内部调用。
类间关系:Passenger与Luggage为组合关系(Luggage不能脱离Passenger独立存在);Flight与Passenger为聚合关系(旅客可独立存在,可乘坐多个航班);WeightBalanceCalculator与Flight、Passenger、CargoCompartment、Cargo为依赖关系(计算需要获取这些类的数据);InputValidator与主流程为依赖关系(主流程调用其获取和校验输入数据)。
2.3.2 源码实现与分析
核心源码片段(新增及优化类):
public class Luggage {
private double weight;
public Luggage(double weight) {
this.weight = weight;
}
public double getWeight() {
return weight;
}
}
public class Passenger {
private String name;
private Luggage luggage;
private static final double STANDARD_WEIGHT = 75.0; // 标准体重75kg
public Passenger(String name, double luggageWeight) {
this.name = name;
this.luggage = new Luggage(luggageWeight);
}
public double getTotalWeight() {
return STANDARD_WEIGHT + luggage.getWeight();
}
public String getName() { return name; }
}
import java.util.List;
public class WeightBalanceCalculator {
public static final double EMPTY_PLANE_WEIGHT = 40000.0; // 空机重量
public static final double EMPTY_PLANE_ARM = 16.25; // 空机重心力臂
public static final double PASSENGER_ARM = 18.0; // 旅客舱平均力臂
public static final double FRONT_CARGO_ARM = 12.0; // 前舱力臂(ID=1)
public static final double REAR_CARGO_ARM = 22.0; // 后舱力臂(ID=2)
public static final double MAC_LENGTH = 5.0; // MAC长度
public static final double MAC_LEADING_EDGE = 15.0; // MAC前缘距基准距离
public static final double MIN_CG_PERCENT = 25.0; // 安全CG%下限
public static final double MAX_CG_PERCENT = 38.0; // 安全CG%上限
public void generateLoadSheet(Flight flight) {
double emptyWeight = EMPTY_PLANE_WEIGHT;
double emptyArm = EMPTY_PLANE_ARM;
int passengerCount = flight.getPassengers().size();
double passengerTotalWeight = flight.getPassengerTotalWeight();
CargoCompartment frontCargo = flight.getCargoCompartmentById("1");
CargoCompartment rearCargo = flight.getCargoCompartmentById("2");
double frontCargoTotalWeight = frontCargo.getCurrentWeight();
double rearCargoTotalWeight = rearCargo.getCurrentWeight();
double totalWeight = calculateTotalWeight(passengerTotalWeight, frontCargoTotalWeight, rearCargoTotalWeight);
double totalMoment = calculateTotalMoment(passengerTotalWeight, frontCargoTotalWeight, rearCargoTotalWeight);
double actualCG = calculateActualCG(totalWeight, totalMoment);
double cgPercent = calculateCGPercent(actualCG);
printLoadSheet(flight.getFlightNo(), emptyWeight, emptyArm, passengerCount, passengerTotalWeight,
frontCargo, rearCargo, totalWeight, totalMoment, actualCG, cgPercent);
}
public double calculateTotalWeight(double passengerTotal, double frontCargoTotal, double rearCargoTotal) {
return EMPTY_PLANE_WEIGHT + passengerTotal + frontCargoTotal + rearCargoTotal;
}
public double calculateTotalMoment(double passengerTotal, double frontCargoTotal, double rearCargoTotal) {
double emptyMoment = EMPTY_PLANE_WEIGHT * EMPTY_PLANE_ARM;
double passengerMoment = passengerTotal * PASSENGER_ARM;
double frontCargoMoment = frontCargoTotal * FRONT_CARGO_ARM;
double rearCargoMoment = rearCargoTotal * REAR_CARGO_ARM;
return emptyMoment + passengerMoment + frontCargoMoment + rearCargoMoment;
}
public double calculateActualCG(double totalWeight, double totalMoment) {
return totalMoment / totalWeight;
}
public double calculateCGPercent(double actualCG) {
return ((actualCG - MAC_LEADING_EDGE) / MAC_LENGTH) * 100;
}
private void printLoadSheet(String flightNo, double emptyWeight, double emptyArm, int passengerCount,
double passengerTotalWeight, CargoCompartment frontCargo, CargoCompartment rearCargo,
double totalWeight, double totalMoment, double actualCG, double cgPercent) {
System.out.println("======================================");
System.out.printf(" 航班 %s 载重平衡舱单%n", flightNo);
System.out.println("======================================");
// 基础数据
System.out.println("【基础数据】");
System.out.printf("空机重量: %.1f kg | 力臂: %.1f m%n", emptyWeight, emptyArm);
// 旅客数据
System.out.println("【旅客数据】");
System.out.printf("旅客人数: %d | 总重(含行李): %.1f kg%n", passengerCount, passengerTotalWeight);
// 货物数据(前舱)
System.out.println("【货物数据】");
System.out.printf("[1] 装载: %.1fkg / %.1fkg (力臂: %.1fm)%n",
frontCargo.getCurrentWeight(), frontCargo.getMaxWeight(), FRONT_CARGO_ARM);
for (Cargo cargo : frontCargo.getCargos()) {
System.out.printf(" ->[%d, %.1fkg]%n", cargo.getId(), cargo.getWeight());
}
// 货物数据(后舱)
System.out.printf("[2] 装载: %.1fkg / %.1fkg (力臂: %.1fm)%n",
rearCargo.getCurrentWeight(), rearCargo.getMaxWeight(), REAR_CARGO_ARM);
for (Cargo cargo : rearCargo.getCargos()) {
System.out.printf(" ->[%d, %.1fkg]%n", cargo.getId(), cargo.getWeight());
}
// 汇总计算
System.out.println("【汇总计算】");
System.out.printf("起飞总重量: %.1f kg%n", totalWeight);
System.out.printf("总力矩:%.1f kg·m%n", totalMoment);
System.out.printf("实际重心(CG): %.1f m%n", actualCG);
System.out.printf("重心百分比(%%MAC): %.1f%%%n", cgPercent);
// 配平评估
System.out.println("======================================");
if (cgPercent >= MIN_CG_PERCENT && cgPercent <= MAX_CG_PERCENT) {
System.out.println("配平评估: 安全 (GREEN)");
} else {
System.out.println("配平评估: 危险 (RED - 超出极限重心范围)");
}
System.out.println("======================================");
}
}
import java.util.Scanner;
public class InputValidator {
private static Scanner scanner = new Scanner(System.in);
public static int getValidatedInt(String prompt, int min, int max) {
System.out.print(prompt);
while (!scanner.hasNextInt()) {
System.out.println("输入错误!请输入整数!");
scanner.next();
}
int num = scanner.nextInt();
if (num < min || num > max) {
System.out.printf("输入必须在 %d 到 %d 之间!%n", min, max);
System.exit(0);
}
return num;
}
public static double getValidatedDouble(String prompt, double min, double max) {
System.out.print(prompt);
while (!scanner.hasNextDouble()) {
System.out.println("输入错误!请输入浮点数!");
scanner.next();
}
double num = scanner.nextDouble();
if (num < min || num > max) {
System.out.printf("输入必须在 %.1f 到 %.1f 之间!%n", min, max);
System.exit(0);
}
return num;
}
public static String getString(String prompt) {
System.out.print(prompt);
scanner.nextLine(); // 接收回车
return scanner.nextLine();
}
public static void closeScanner() {
scanner.close();
}
}
源码分析:本次源码是三次作业中最复杂的一次,新增3个类,优化1个类,原有类进行扩展,但所有类均严格遵循单一职责原则,无职责交叉。例如,Passenger类仅负责旅客信息管理和总重量计算,Luggage类仅负责行李重量记录,两者组合关系的设计符合题目要求;WeightBalanceCalculator类作为纯计算工具类,不存储任何数据,仅通过传入Flight对象获取所需数据,进行配平计算,完全遵循依赖关系的设计;InputValidator类优化后,统一了输入获取与校验逻辑,剥离了控制台异常处理,职责更加单一。
从SourceMonitor报表来看(此处插入报表截图,描述:代码行数1256行,类数量10个,方法数量42个,平均方法长度18行,代码复杂度较高,但无重复代码,类间耦合度低),代码结构清晰,职责划分明确,所有方法均围绕所属类的核心职责展开。其中,WeightBalanceCalculator类的配平计算逻辑严格按照题目要求的步骤实现,公式应用准确;冒泡排序算法在LoadDispatcher类中保持不变,被WeightBalanceCalculator类内部调用;输入校验逻辑在InputValidator类中统一实现,确保所有输入数据的合法性,提高了代码的鲁棒性。
设计心得:本次作业的核心是复杂业务逻辑的拆解、类间关系的精准设计以及单一职责原则的深度应用。配平计算涉及多个步骤和专业公式,通过将计算逻辑拆分到WeightBalanceCalculator类的不同方法中,使得每个方法的职责单一,便于理解和维护;类间关系的设计(尤其是组合关系和依赖关系)严格遵循题目要求,确保代码的规范性;输入校验的统一实现,避免了代码冗余,提高了代码的可复用性。同时,本次作业禁止使用继承和多态,要求我们专注于类的职责划分和类间关系的设计,进一步锤炼了面向对象设计的基本功。
三、采坑心得
三次作业的编码与提交过程中,我遇到了诸多问题,涉及类设计、业务逻辑、输入处理、算法实现、数据校验等多个方面。以下将结合具体的问题场景、代码片段、测试数据及解决方案,详实总结采坑心得,避免后续出现类似错误,同时深化对面向对象设计和编码规范的理解。
3.1 7-1作业采坑心得
3.1.1 坑点1:违背单一职责原则,将业务逻辑与数据存储合并到一个类中
问题描述:第一次提交时,我将Flight类和CargoLoader类的职责合并,在Flight类中不仅封装了航班信息,还实现了货物添加、排序、总重量计算等业务逻辑,导致Flight类职责过重,违背了单一职责原则,代码提交后被判零分。
错误代码片段(简化):
// 错误设计:Flight类承担了数据存储和业务逻辑双重职责
public class Flight {
private String flightNo;
private double maxWeight;
private double currentTotalWeight;
private List<Cargo> cargoList;
// 构造器
public Flight(String flightNo, double maxWeight) {
this.flightNo = flightNo;
this.maxWeight = maxWeight;
this.currentTotalWeight = 0.0;
this.cargoList = new ArrayList<>();
}
// 货物添加(业务逻辑)
public void addCargo(Cargo cargo) {
cargoList.add(cargo);
sortCargos();
updateTotalWeight();
}
// 排序(业务逻辑)
public void sortCargos() {
// 排序逻辑...
}
// 计算总重量(业务逻辑)
public void updateTotalWeight() {
// 计算逻辑...
}
// 其他getter、setter方法
}
问题分析:这种设计违背了单一职责原则,Flight类既负责存储航班和货物数据,又负责处理货物添加、排序等业务逻辑,导致类的复杂度升高,可维护性降低。例如,若需要修改排序算法,必须修改Flight类;若需要修改航班信息的存储方式,可能会影响到业务逻辑的实现。
解决方案:拆分职责,将数据存储与业务逻辑分离,创建Flight类(仅负责数据封装)、Cargo类(仅负责货物信息存储)、CargoLoader类(仅负责业务逻辑处理),确保每个类的职责单一。修改后的代码如2.1.2节所示,提交后顺利通过测试。
测试数据验证:以输入样例为例,修改后的代码能够正确读取航班信息和货物数据,按重量降序排序并输出,计算总重量并判断超载状态,输出结果与样例完全一致。
心得:单一职责原则是面向对象设计的基础,必须严格遵守。类的职责越单一,代码的可维护性、可扩展性就越强,后续迭代也越方便。在编码前,应先明确每个类的核心职责,避免职责交叉和过度耦合。
3.1.2 坑点2:输入处理异常,nextLine()接收回车导致数据读取错误
问题描述:编码时,未按照题目提示处理输入,在使用nextInt()、nextDouble()后未添加nextLine()接收回车,导致后续的nextLine()读取到空字符串,货物名称读取错误,程序运行异常。
错误代码片段:
// 错误输入处理
int n = scanner.nextInt();
// 未添加nextLine()接收回车
String cargoName = scanner.nextLine(); // 读取到的是回车,而非货物名称
问题分析:nextInt()、nextDouble()方法读取数据后,会将输入的回车留在输入缓冲区中,后续的nextLine()方法会直接读取这个回车,导致读取到的内容为空,进而导致货物名称错误,程序无法正常运行。
解决方案:按照题目提示,在nextInt()、nextDouble()后添加nextLine()接收回车,确保后续的nextLine()能够正确读取输入的字符串。修改后的代码如下:
int n = scanner.nextInt();
scanner.nextLine(); // 接收回车
String cargoName = scanner.nextLine(); // 正确读取货物名称
测试数据验证:使用输入样例测试,修改后能够正确读取航班号、最大载重、货物件数、货物名称和重量,程序运行正常,输出结果正确。
心得:输入处理是编码中的基础环节,必须注意不同输入方法的特性,避免因输入缓冲区残留数据导致读取错误。在使用Scanner类进行输入时,应严格按照题目提示或编码规范处理回车问题,确保数据读取的准确性。
3.1.3 坑点3:排序逻辑错误,未按货物重量从高到低排序
问题描述:第一次实现sortCargos方法时,误将排序逻辑写为“按重量升序排序”,导致输出的货物顺序与题目要求相反,测试结果不通过。
错误代码片段:
// 错误排序逻辑(升序)
if (cargoList.get(j).getCargoWeight() > cargoList.get(j + 1).getCargoWeight()) {
// 交换位置
Cargo temp = cargoList.get(j);
cargoList.set(j, cargoList.get(j + 1));
cargoList.set(j + 1, temp);
}
问题分析:题目要求按货物重量从高到低排序,而上述代码中,当当前货物重量大于下一个货物重量时进行交换,实际实现的是升序排序,与题目要求不符。
解决方案:修改排序条件,将“>”改为“<”,当当前货物重量小于下一个货物重量时进行交换,实现降序排序。修改后的代码如下:
// 正确排序逻辑(降序)
if (cargoList.get(j).getCargoWeight() < cargoList.get(j + 1).getCargoWeight()) {
// 交换位置
Cargo temp = cargoList.get(j);
cargoList.set(j, cargoList.get(j + 1));
cargoList.set(j + 1, temp);
}
测试数据验证:输入样例中,货物重量分别为500、850、128,排序后应为850、500、128,修改后输出顺序正确,与样例一致。
心得:编码时应仔细阅读题目要求,明确业务逻辑的细节,避免因粗心导致逻辑错误。对于排序、计算等核心逻辑,编码完成后应手动梳理逻辑流程,或通过简单的测试数据验证逻辑的正确性。
3.2 7-2作业采坑心得
3.2.1 坑点1:类间关系设计错误,将聚合关系改为组合关系
问题描述:在设计CargoCompartment类与Cargo类的关系时,误将聚合关系设计为组合关系,在CargoCompartment的构造器中创建Cargo对象,导致Cargo对象无法独立存在,违背了题目要求,代码提交后部分测试用例失败。
错误代码片段:
// 错误设计:CargoCompartment与Cargo为组合关系(Cargo无法独立存在)
public class CargoCompartment {
private List<Cargo> cargos;
public CargoCompartment(String id, double maxWeight, int rowCount, int colCount) {
// 其他初始化逻辑...
this.cargos = new ArrayList<>();
// 错误:在货舱构造器中创建Cargo对象,Cargo无法独立存在
this.cargos.add(new Cargo("test", 100.0));
}
}
问题分析:题目明确要求“货舱与装载的货物是聚合关系(Cargo可独立存在)”,而上述设计中,Cargo对象在CargoCompartment的构造器中创建,无法脱离CargoCompartment独立存在,属于组合关系,违背了题目要求,导致货物无法被多个货舱装载,部分测试用例无法通过。
解决方案:修改类间关系,将CargoCompartment与Cargo改为聚合关系,Cargo对象由主流程创建,通过addCargo方法添加到CargoCompartment中,Cargo对象可独立存在。修改后的代码如2.2.2节所示,CargoCompartment类的cargos列表初始为空,通过addCargo方法添加外部创建的Cargo对象。
测试数据验证:使用输入样例2测试,货物X和货物Y被添加到货舱C中,货物对象独立创建,添加成功后,货舱总重量计算正确,输出结果与样例一致。
心得:类间关系(组合、聚合、依赖)的设计是面向对象设计的重点,必须严格按照题目要求或业务场景进行设计。聚合关系与组合关系的核心区别在于“是否可独立存在”,编码前应明确类间关系的类型,避免设计错误。
3.2.2 坑点2:货物排序后,未按排序后的顺序输出装载结果
问题描述:编码时,LoadDispatcher类的sortCargos方法正确实现了货物降序排序,但在主流程中,未使用排序后的货物列表进行装载和输出,而是直接使用原始输入顺序,导致输出的装载结果顺序错误,测试用例失败。
错误代码片段:
// 错误:未使用排序后的货物列表
List<Cargo> cargoList = new ArrayList<>();
// 循环添加货物(原始顺序)
for (int i = 0; i < n; i++) {
// 读取货物信息并添加到cargoList
}
// 排序(仅排序,未使用排序后的列表)
LoadDispatcher dispatcher = new LoadDispatcher();
dispatcher.sortCargos(cargoList);
// 错误:仍使用原始顺序装载和输出
for (Cargo cargo : cargoList) {
// 装载和输出逻辑...
}
问题分析:虽然调用了sortCargos方法对货物列表进行排序,但排序后的列表未被正确使用,主流程仍使用原始输入顺序进行装载和输出,导致输出的货物顺序与题目要求的“按重量降序排序后输出”不符。
解决方案:确保排序后的货物列表被正确使用,排序后循环遍历排序后的列表进行装载和输出,无需额外创建新的列表(sortCargos方法直接修改原列表顺序)。修改后的代码如下:
List<Cargo> cargoList = new ArrayList<>();
// 循环添加货物(原始顺序)
for (int i = 0; i < n; i++) {
// 读取货物信息并添加到cargoList
}
// 排序(修改原列表顺序)
LoadDispatcher dispatcher = new LoadDispatcher();
dispatcher.sortCargos(cargoList);
// 正确:使用排序后的列表装载和输出
for (Cargo cargo : cargoList) {
// 装载和输出逻辑...
}
测试数据验证:输入样例1中,货物重量分别为850、500、128,排序后顺序正确,输出的装载结果顺序与样例一致,测试通过。
心得:编码时应注意逻辑的连贯性,确保每一步操作的结果都被正确使用。对于排序、过滤等操作,应明确操作后的列表状态,避免出现“排序后未使用”“过滤后未生效”等逻辑漏洞。
3.2.3 坑点3:货舱超载判断错误,未考虑当前重量与货物重量的总和
问题描述:在CargoCompartment类的addCargo方法中,误将“当前重量是否超过最大载重”作为判断条件,而非“当前重量+货物重量是否超过最大载重”,导致货物装载判断错误,部分货物本应装载失败却被成功装载,测试用例失败。

浙公网安备 33010602011771号