面向对象设计与构造——第一单元总结
Java课程的第一单元结束了,第一单元,以多项式求导程序为中心,不断增加新的功能,我学到了很多:
1.熟悉了Java基本语法;
2.初步建立起了面向对象的设计思路;
3.认识到了代码可拓展性的重要;
4.学会了构建自动化测试工具。
Complexity Metrics(复杂度分析)
因为下面要用到复杂度分析,所以先在此给出一些相关概念。
我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。
ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在[1,v(G)]之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在[1,v(G)]之间,值越大联系越紧密。
v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
下面我将从程序结构,公测、互测以及bug分析几个方面来总结我第一单元的三次作业。
第一次作业
(1)作业要求
航班信息管理:记录航班号、最大载重重量,提供基础的get/set方法,支持航班信息的初始化与获取;
货物管理:支持输入货物名称、重量,按货物重量从高到低的顺序装载;
重量计算与超载判断:实时统计已装载货物的总重量,对比航班最大载重,判断是否超载;
格式化输出:按指定格式输出货物装载信息、总重量、最大载重及配载状态,所有数值保留1位小数。
实现方式
输入:第1行输入航班号,第2行输入最大载重,第3行输入货物件数,后续依次输入每件货物的名称和重量;
输出:按装载顺序输出每件货物信息,随后输出总重量与最大载重对比,最后输出配载状态。
LoadManifest依赖Flight、Cargo、CargoSorter,通过调用其他类的方法完成核心业务,无耦合过高问题;Main类作为程序入口,仅负责输入处理与方法调用,不承担其他业务逻辑。
代码规模
第一次作业代码规模如下图。

(2)类图

(3)复杂度分析
第一次作业的复杂度分析如下。



代码解析
(1)货物排序实现(CargoSorter类)
采用选择排序算法,按货物重量从高到低排序,避免使用Java自带排序方法,贴合作业要求:
public void sort() {
if(count<=1){
return;
}
for(int i=0;i<count-1;i++){
int maxCount=i;
for (int j=i+1;j<count;j++){
if (cargos[j].getCargoWeight()>cargos[maxCount].getCargoWeight()){
maxCount=j;
}
}
Cargo temp=cargos[i];
cargos[i]=cargos[maxCount];
cargos[maxCount]=temp;
}
}
解析:通过双重循环,每次找到未排序部分的最大重量货物,与当前位置交换,实现降序排序,逻辑清晰,符合选择排序的核心思想;同时添加边界判断(货物件数≤1时无需排序),提升代码鲁棒性。
(2)货物装载与总重量计算(LoadManifest类)
addCargo()方法负责添加货物并实时累加总重量,cargoSort()方法调用CargoSorter完成排序,isOverWeight()方法判断是否超载:
public void addCargo(Cargo cargo){
cargos[cargoCount]=cargo;
cargoCount++;
totalWeight+=cargo.getCargoWeight();
}
public void cargoSort(){
CargoSorter sorter=new CargoSorter(cargos,cargoCount);
sorter.sort();
}
public int isOverWeight(){
if(totalWeight>flight.getMaxWeight()){
return 1;
}
else{
return 0;
}
}
解析:每次添加货物时,将货物存入数组,同时更新总重量,实现实时统计;超载判断通过对比总重量与航班最大载重,返回1(超载)或0(正常),逻辑简洁,职责明确。
(3)输入处理(Main类)
重点处理Scanner输入的回车残留问题,确保输入数据正确获取:
int count=sc.nextInt();
sc.nextLine();// 接收回车残留
for(int i=0;i<count;i++){
sc.nextLine();// 接收上一次输入的回车
String name=sc.nextLine();
double weight=sc.nextDouble();
Cargo cargo=new Cargo(name,weight);
calCargo.addCargo(cargo);
}
解析:nextInt()、nextDouble()方法后会残留回车,通过额外调用nextLine()接收回车,避免后续nextLine()获取到空字符串,这是Java Scanner输入的常见坑点,此处处理到位。
(4)测试验证与Bug复盘
1. 测试方法
使用Python xeger包根据正则表达式生成测试数据,sympy库计算正确输出,调用Java程序输出,通过equals方法比对结果;同时手动阅读代码,定位他人代码漏洞。
2. Bug复盘
-
公测Bug:输出优化不彻底,未省略“-1*x”中的系数1,导致性能分未满分;
-
互测被Hack:输出逻辑缺陷,仅处理单个零项,多个零项叠加时无输出;
-
主动Hack:成功攻击17次,核心问题均为正则表达式使用错误,开发者对输入边界场景(如正负号、空格、特殊字符)考虑不全。
(5)经验总结
本次作业实现了基础的单货舱配载功能,核心收获是熟悉了Java基础语法和集合使用,初步践行了单一职责原则;不足在于结构化设计薄弱,条件语句滥用,正则表达式使用不熟练,后续需重点优化代码结构,关注输入边界场景。
第二次作业
(1)作业要求
航班信息管理:新增最大起飞重量、最大业载重量,关联多个货舱,统计航班整体总重量;
货舱管理:每个货舱拥有唯一标识、最大载重、行列网格(位置),货舱与位置为组合关系(货舱创建时生成位置),货舱与货物为聚合关系(货物可独立存在);
货物装载:输入货物时指定目标货舱ID,先按货物重量从高到低排序,再尝试装入指定货舱,判断货舱是否超载;
双重超载判断:分别判断每个货舱是否超载、航班整体是否超过最大起飞重量或最大业载重量;
格式化输出:输出货物装载结果(成功/失败)、每个货舱的装载统计与状态、航班整体重量与配载状态。
实现方法
严格遵循单一职责原则,必选类包括:Position(位置)、Cargo(货物)、CargoCompartment(货舱)、Flight(航班)、LoadDispatcher(调度)、InputValidator(输入校验);
明确类间关系:货舱与位置为组合关系,货舱与货物为聚合关系,调度类与货物为依赖关系。
代码规模
第二次作业的代码规模如下图。

(2)类图

复杂度分析
第二次作业的复杂度分析如下。




(3)代码解析
1. 货舱与位置实现(CargoCompartment、Position类)
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;
}
}
class CargoCompartment {
private String id;
private double maxWeight;
private List<Position> positions;
private List<Cargo> cargos;
public CargoCompartment(String id, double maxWeight, int rows, int cols){
this.id = id;
this.maxWeight = maxWeight;
this.cargos = new ArrayList<>();
this.positions = new ArrayList<>();
// 组合关系:货舱创建时生成所有位置
for(int i=1;i<=rows;i++){
for (int j=1;j<=cols;j++){
positions.add(new Position(i,j));
}
}
}
// 计算当前货舱已装载重量
public double getCurrentWeight(){
double sum = 0;
for (Cargo cargo : cargos) {
sum += cargo.getWeight();
}
return sum;
}
// 判断是否超载
public boolean isOverweight(){
return getCurrentWeight() > maxWeight;
}
// 尝试添加货物
public boolean addCargo(Cargo cargo){
if (getCurrentWeight() + cargo.getWeight() <= maxWeight) {
cargos.add(cargo);
return true;
}
return false;
}
// get/set方法
public String getId() { return id; }
public double getMaxWeight() { return maxWeight; }
public List<Cargo> getCargos() { return cargos; }
}
2. 货物排序调度(LoadDispatcher类)
class LoadDispatcher{
// 选择排序,按货物重量降序
public List<Cargo> sortCargos(List<Cargo> cargoList){
List<Cargo> sortedList = new ArrayList<>();
for (int i=0;i<cargoList.size();i++){
sortedList.add(cargoList.get(i));
}
int size = sortedList.size();
for (int i=0;i<size-1;i++){
int maxIndex = i;
for (int j=i+1;j<size;j++){
if (sortedList.get(j).getWeight() > sortedList.get(maxIndex).getWeight()){
maxIndex = j;
}
}
// 交换位置
Cargo temp = sortedList.get(maxIndex);
sortedList.set(maxIndex, sortedList.get(i));
sortedList.set(i, temp);
}
return sortedList;
}
}
3. 输入校验(InputValidator类)
class InputValidator{
public boolean validateInt(int value,int min,int max){
if(value>=min&&value<=max){
return true;
} else {
return false;
}
}
public boolean validateDouble(double value,double min){
if(value>=min){
return true;
} else {
return false;
}
}
}
4. 主流程实现(Main类)
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
InputValidator validator = new InputValidator();
LoadDispatcher dispatcher = new LoadDispatcher();
// 输入航班信息
String flightNo = sc.nextLine();
double maxTakeoff = Double.parseDouble(sc.nextLine());
double maxPayload = Double.parseDouble(sc.nextLine());
// 输入货舱数量及信息
int m = Integer.parseInt(sc.nextLine());
List<CargoCompartment> compartments = new ArrayList<>();
for (int i=0;i<m;i++){
String[] parts = sc.nextLine().split(" ");
String id = parts[0];
double maxW = Double.parseDouble(parts[1]);
int row = Integer.parseInt(parts[2]);
int col = Integer.parseInt(parts[3]);
compartments.add(new CargoCompartment(id, maxW, row, col));
}
// 输入货物信息
int n = Integer.parseInt(sc.nextLine());
List<Cargo> cargos = new ArrayList<>();
for (int i=0;i<n;i++){
String[] parts = sc.nextLine().split(" ");
String name = parts[0];
double w = Double.parseDouble(parts[1]);
String target = parts[2];
cargos.add(new Cargo(name, w, target));
}
// 货物排序并装载
List<Cargo> sortedCargos = dispatcher.sortCargos(cargos);
System.out.println("===== 货物按重量降序排序后装载结果 =====");
for(int i=0;i<sortedCargos.size();i++){
Cargo cargo = sortedCargos.get(i);
CargoCompartment target = null;
// 找到目标货舱
for(int j=0;j<compartments.size();j++){
CargoCompartment c = compartments.get(j);
if(c.getId().equals(cargo.getTargetHold())){
target = c;
break;
}
}
boolean success = false;
if (target != null){
success = target.addCargo(cargo);
}
// 输出装载结果
if(success){
System.out.printf("货物[%s 重量:%.1fkg] → 装入货舱[%s] 成功%n",
cargo.getId(), cargo.getWeight(), cargo.getTargetHold());
} else {
System.out.printf("货物[%s 重量:%.1fkg] → 装入货舱[%s] 失败 (超载)%n",
cargo.getId(),cargo.getWeight(),cargo.getTargetHold());
}
}
// 输出货舱与航班状态
System.out.println();
Flight flight = new Flight(flightNo, maxTakeoff, maxPayload, compartments);
for(int i=0;i<compartments.size();i++){
CargoCompartment c = compartments.get(i);
String status = c.isOverweight() ? "超载" : "正常";
System.out.printf("货舱[%s] 已装重量: %.1fkg / 最大载重: %.1fkg → 状态: %s%n",
c.getId(), c.getCurrentWeight(), c.getMaxWeight(), status);
}
double totalWeight = flight.getTotalWeight();
System.out.printf("航班总重量: %.1fkg / 最大起飞重量: %.1fkg / 最大业载: %.1fkg%n",
totalWeight, flight.getMaxTakeoffWeight(), flight.getMaxPayloadWeight());
// 判断航班整体超载
boolean overTakeoff = totalWeight > flight.getMaxTakeoffWeight();
boolean overPayload = totalWeight > flight.getMaxPayloadWeight();
if (overTakeoff&&overPayload){
System.out.print("警告:超过最大起飞重量和最大业载重量");
} else if(overTakeoff){
System.out.print("警告:超过最大起飞重量");
} else if(overPayload){
System.out.print("警告:超过最大业载重量");
} else{
System.out.print("整体配载状态:正常");
}
sc.close();
}
}
(4)测试验证与Bug复盘
测试方法
沿用V1的自动化测试框架,优化测试数据生成逻辑,增加多货舱、超载场景的测试用例;但因sympy库的equals方法存在缺陷,导致部分等价表达式无法正确判断,影响测试效率。
2. Bug复盘
-
公测Bug:未进行贪心拆项优化,仅完成三角函数合并,性能分未满分;
-
互测被Hack:4个核心Bug,分别是HashMap的remove()方法使用异常、Matcher的matches()方法误用(应使用find()检测空白字符)、正则表达式贪婪模式导致TLE;
-
未主动Hack:因自动化测试工具(对拍器)出现问题,无法正确判断表达式等价性,导致未发现他人Bug。
(5)经验总结
本次作业实现了多货舱管理功能,核心收获是掌握了类间组合、聚合关系的设计与实现,进一步深化了单一职责原则的应用;不足在于代码复杂度控制不佳,部分方法条件语句嵌套过多,测试工具优化不足,后续需重点拆分复杂方法,提升测试工具的稳定性。
第三次作业
(1)作业要求
旅客与行李管理:新增旅客实体,旅客包含标准体重(75kg)与随身行李,旅客与行李为组合关系(行李在旅客构造器中创建);
载重平衡计算:根据航空力学公式,计算旅客总重量、货舱总重量、全机总重量、总力矩、实际重心、重心百分比(%MAC),评估配平安全性;
鲁棒性增强:输入过程中进行合法性校验,非法输入(负数、超出范围)立即终止程序,货舱装载量超出最大载重时给出警告并终止;
标准化输出:输出标准的载重平衡舱单,包含基础数据、旅客数据、货物数据、汇总计算、配平评估。
实现方法
严格遵循单一职责原则,新增类包括:Luggage(行李)、Passenger(旅客)、WeightBalanceCalculator(载重平衡计算),优化InputValidator类;
禁止使用继承、多态(无extends、无interface、无instanceof);
排序算法必须使用循环实现(冒泡排序),禁止使用Lambda表达式及Collections.sort()方法;
计算类为纯工具类,不持有Flight成员变量,仅通过方法参数传入Flight对象。
代码规模
第三次作业的代码规模如下图。

(2)类图

(3)复杂度分析
第三次作业的复杂度分析如下。




(4)代码解析
1. 旅客与行李实现(Passenger、Luggage类)
class Luggage{
private double weight;
public Luggage(double weight){
this.weight = weight;
}
public double getWeight(){
return weight;
}
}
class Passenger{
public static final double STANDARD_WEIGHT = 75.0;
private Luggage luggage;
// 组合关系:构造器内部创建行李,不对外暴露
public Passenger(double luggageWeight){
this.luggage = new Luggage(luggageWeight);
}
// 计算旅客总重量(标准体重+行李重量)
public double getTotalWeight(){
return STANDARD_WEIGHT + luggage.getWeight();
}
}
2. 载重平衡计算(WeightBalanceCalculator类)
class WeightBalanceCalculator{
// 系统预设静态常量
public static final double EMPTY_WEIGHT = 40000.0;
public static final double EMPTY_ARM = 16.25;
public static final double PASSENGER_ARM = 18.0;
public static final double FRONT_ARM = 12.0;
public static final double REAR_ARM = 22.0;
public static final double MAC_LEN = 5.0;
public static final double MAC_FRONT = 15.0;
public static final double CG_MIN = 25.0;
public static final double CG_MAX = 38.0;
// 纯工具类方法,传入Flight对象完成计算与输出
public void calcAndPrint(Flight flight){
// 第一步:计算旅客总重量及力矩
double passTotalW = 0.0;
List<Passenger> pList = flight.getPassengers();
for(int i=0;i<pList.size();i++){
passTotalW += pList.get(i).getTotalWeight();
}
double passMoment = passTotalW * PASSENGER_ARM;
// 第二步:计算各货舱总重量及力矩
CargoCompartment front = flight.getFrontComp();
CargoCompartment rear = flight.getRearComp();
double frontW = front.getCurrentWeight();
double rearW = rear.getCurrentWeight();
double cargoMoment = frontW * FRONT_ARM + rearW * REAR_ARM;
// 第三步:计算全机总重量与总力矩
double totalW = EMPTY_WEIGHT + passTotalW + frontW + rearW;
double totalM = EMPTY_WEIGHT * EMPTY_ARM + passMoment + cargoMoment;
// 第四步:计算实际重心
double cg = totalM / totalW;
// 第五步:换算重心百分比并评估
double cgPercent = ((cg - MAC_FRONT) / MAC_LEN) * 100;
// 输出载重平衡舱单
System.out.println("======================================");
System.out.printf(" 航班 %s 载重平衡舱单%n", flight.getFlightNo());
System.out.println("======================================");
System.out.println("【基础数据】");
System.out.printf("空机重量: %.1f kg | 力臂: %.1f m%n", EMPTY_WEIGHT, EMPTY_ARM);
System.out.println("【旅客数据】");
System.out.printf("旅客人数: %d | 总重(含行李): %.1f kg%n", pList.size(), passTotalW);
System.out.println("【货物数据】");
// 前舱货物输出
System.out.printf("[1] 装载: %.1fkg / %.1fkg (力臂: %.1fm)%n",
front.getCurrentWeight(), front.getMaxWeight(), FRONT_ARM);
List<Cargo> frontCargos = front.getCargoList();
for(int i=0;i<frontCargos.size();i++){
Cargo c = frontCargos.get(i);
System.out.printf(" ->[%d, %.1fkg]%n", c.getId(), c.getWeight());
}
// 后舱货物输出
System.out.printf("[2] 装载: %.1fkg / %.1fkg (力臂: %.1fm)%n",
rear.getCurrentWeight(), rear.getMaxWeight(), REAR_ARM);
List<Cargo> rearCargos = rear.getCargoList();
for(int i=0;i<rearCargos.size();i++){
Cargo c2 = rearCargos.get(i);
System.out.printf(" ->[%d, %.1fkg]%n", c2.getId(), c2.getWeight());
}
// 汇总计算输出
System.out.println("【汇总计算】");
System.out.printf("起飞总重量: %.1f kg%n", totalW);
System.out.printf("总力矩:%.1f kg·m%n", totalM);
System.out.printf("实际重心(CG): %.1f m%n", cg);
System.out.printf("重心百分比(%%MAC): %.1f%%%n", cgPercent);
System.out.println("======================================");
// 配平评估
if(cgPercent >= CG_MIN && cgPercent <= CG_MAX){
System.out.println("配平评估: 安全 (GREEN)");
} else {
System.out.println("配平评估: 危险 (RED - 超出极限重心范围)");
}
System.out.println("======================================");
}
}
3. 输入校验优化(InputValidator类)
class InputValidator{
public static Scanner sc = new Scanner(System.in);
// 校验非负整数
public static int getIntNotNegative(){
int m = sc.nextInt();
if(m < 0){
System.out.println("数值不能为负数!");
System.exit(0);
}
return m;
}
// 校验非负浮点数
public static double getDoubleNotNegative(){
double m = sc.nextDouble();
if(m < 0){
System.out.println("数值不能为负数!");
System.exit(0);
}
return m;
}
// 校验整数范围
public static int getIntRange(int min,int max){
int m = sc.nextInt();
if(m < min || m > max){
System.out.printf("输入必须在 %d 到 %d 之间!%n", min, max);
System.exit(0);
}
return m;
}
}
4. 主流程实现(Main类)
public class Main{
public static void main(String[] args){
Scanner sc = InputValidator.sc;
// 输入航班号
String flightNo = sc.next();
// 输入前舱信息
int fRow = InputValidator.getIntNotNegative();
int fCol = InputValidator.getIntNotNegative();
double fMaxW = InputValidator.getDoubleNotNegative();
CargoCompartment front = new CargoCompartment(1, fRow, fCol, fMaxW);
// 输入后舱信息
int rRow = InputValidator.getIntNotNegative();
int rCol = InputValidator.getIntNotNegative();
double rMaxW = InputValidator.getDoubleNotNegative();
CargoCompartment rear = new CargoCompartment(2, rRow, rCol, rMaxW);
// 输入旅客信息
int pCount = InputValidator.getIntNotNegative();
Flight flight = new Flight(flightNo);
flight.setCompartment(front, rear);
for(int i=0;i<pCount;i++){
double luggageW = InputValidator.getDoubleNotNegative();
flight.addPassenger(new Passenger(luggageW));
}
// 输入货物信息
int m = InputValidator.getIntNotNegative(); // 货物总件数
int p = InputValidator.getIntNotNegative(); // 前舱货物件数
// 校验前舱货物件数范围
if(p < 0 || p > m){
System.out.printf("输入必须在 0 到 %d 之间!%n", m);
System.exit(0);
}
// 校验前舱货物件数不超过货架容量
int frontMaxCount = front.getMaxCount();
if(p > frontMaxCount){
System.out.printf("输入必须在 0 到 %d 之间!%n", frontMaxCount);
System.exit(0);
}
// 校验后舱货物件数范围
int rearNeedCount = m - p;
int rearMaxCount = rear.getMaxCount();
if(rearNeedCount < 0 || rearNeedCount > rearMaxCount){
System.out.printf("输入必须在 0 到 %d 之间!%n", rearMaxCount);
System.exit(0);
}
// 装载前舱货物
double frontPlanWeight = 0;
for (int i=0;i<p;i++){
int id = InputValidator.getIntNotNegative();
double w = InputValidator.getDoubleNotNegative();
frontPlanWeight += w;
front.safeAddCargo(new Cargo(id, w));
}
// 装载后舱货物
double rearPlanWeight = 0;
for(int i=0;i<rearNeedCount;i++){
int id = InputValidator.getIntNotNegative();
double w = InputValidator.getDoubleNotNegative();
rearPlanWeight += w;
rear.safeAddCargo(new Cargo(id, w));
}
// 校验货舱是否超载
if(frontPlanWeight > front.getMaxWeight()){
System.out.printf("!!! 警告:[1]剩余容量不足(当前载重%.1f/%.1fkg),请重新分配或减轻重量!%n",
front.getCurrentWeight(), front.getMaxWeight());
System.exit(0);
}
if(rearPlanWeight > rear.getMaxWeight()){
System.out.printf("!!! 警告:[2]剩余容量不足(当前载重%.1f/%.1fkg),请重新分配或减轻重量!%n",
rear.getCurrentWeight(), rear.getMaxWeight());
System.exit(0);
}
// 货物排序并输出载重平衡舱单
flight.sortAllCargo();
WeightBalanceCalculator calc = new WeightBalanceCalculator();
calc.calcAndPrint(flight);
sc.close();
}
}
(4)测试验证与Bug复盘
1\测试方法
采用黑箱测试,随机生成带嵌套的测试用例,结合手动造测试集(针对性测试边界场景);正确性验证采用sympy带入100个随机数计算,格式错误通过Python异常处理判定,提升测试的全面性。
2. Bug复盘
-
公测Bug:未优化多余括号,输出长度未达到最优,未获得性能分;
-
互测表现:未被发现Bug,成功发现他人3个Bug(指数范围判断错误、常数求导无输出、格式错误),其中2个通过手动造测试集发现。
(5)经验总结
本次作业实现了完整的航空配载业务,核心收获是掌握了复杂业务逻辑的落地方法,强化了代码鲁棒性和工程化思维,进一步优化了类设计和测试方法;不足在于代码复杂度控制仍有提升空间,设计模式运用不足,后续需重点学习设计模式,优化复杂方法的拆分,提升代码的可扩展性和性能。
4.关于设计模式的思考
航班配载系列作业是Java课程第一单元的核心实践,三个迭代版本层层递进,从基础的货物装载到复杂的载重平衡计算,每一次迭代都带来了新的挑战,也让我获得了显著的成长。在这个过程中,我深刻体会到了面向对象设计的魅力,明白了单一职责原则对代码可维护性、可扩展性的重要意义,也积累了丰富的代码实现、测试验证、Bug排查经验。
虽然目前的代码还存在诸多不足,但通过本次系列作业,我已经建立起了工程化的开发思维,掌握了面向对象编程的基础方法。在后续的学习中,我将针对自身不足,不断优化代码设计,提升技术能力,努力写出更规范、更高效、更具扩展性的代码,为后续的Java课程学习和实际项目开发打下坚实的基础。

浙公网安备 33010602011771号