题目集567总结
一.前言
在这三次题目集中,我们系统性地运用了已掌握的面向对象编程知识来解决实际问题。题目集5作为基础训练,其前四题着重于代码能力的恢复性训练和正则表达式的实践应用,主要考察基础数学函数和逻辑结构的编写能力。从第五题开始,课程内容正式引入了面向对象编程范式,通过实现电梯调度算法的LOOK算法,要求我们建立对象和类的概念,初步培养面向对象的编程思维。
题目集6标志着面向对象思维的强化训练阶段。其中第一、二题旨在深化面向对象的思维方式,要求我们从根本上转变问题解决思路,从面向过程转向面向对象的设计模式。第三题作为题目集5第五题的进阶版本,在难度和复杂度上都有显著提升,进一步巩固了面向对象的编程能力。
题目集7虽然题量精简,但难度达到了最高水平。前两题延续了面向对象思维的训练目标,而第三题则在题目集6第三题的基础上进行了更高层次的迭代开发,要求我们综合运用所学知识解决更复杂的实际问题。这三次作业呈现出清晰的渐进式学习路径,从基础能力恢复到面向对象思维的建立,再到复杂问题的面向对象解决方案设计.
二.设计与分析
1. 求解一元二次方程
我认为这是第一道要将类的结构分离的很清楚的题目,就是面向对象很好做面向过程不好做的题目,题目要求
定义一个代表一元二次方程ax2+bx+c=0的类QuadraticEquation,其属性为三个系数a、b、c(均为私有属性),类中定义的方法参考main方法中的代码。
main方法源码:
import java.util.Scanner; public class Main { public static void main(String[] args){ Scanner input = new Scanner(System.in); double a = Double.parseDouble(input.next()); double b = Double.parseDouble(input.next()); double c = Double.parseDouble(input.next()); if(a == 0){ System.out.println("Wrong Format"); System.exit(0); } QuadraticEquation equation = new QuadraticEquation(a, b, c); double discriminant = equation.getDiscriminant(); System.out.println("a=" + equation.getA() + ",b=" + equation.getB() + ",c=" + equation.getC()+":"); if (discriminant < 0) { System.out.println("The equation has no roots."); } else if (discriminant == 0) { System.out.println("The root is " + String.format("%.2f", equation.getRoot1())); } else // (discriminant >= 0) { System.out.println("The roots are " + String.format("%.2f", equation.getRoot1()) + " and " + String.format("%.2f", equation.getRoot2())); } } } class QuadraticEquation{ //your code }
输入格式:
在一行中输入a、b、c的值,可以用一个或多个空格或回车符分开。
输出格式:
- 当输入非法时,输出“Wrong Format”
- 当有一个实根时,输出(2行):
- a=值,b=值,c=值:
- The root is 值(保留两位小数)
- 当有两个实根时,输出(2行):
- a=值,b=值,c=值:
- The roots are 值1 and 值2(均保留两位小数)
QuadraticEquation类:
class QuadraticEquation { private double a; private double b; private double c; public QuadraticEquation(double a, double b, double c) { this.a = a; this.b = b; this.c = c; } public double getA() { return a; } public double getB() { return b; } public double getC() { return c; } public double getDiscriminant() { return b * b - 4 * a * c; } public double getRoot1() { double discriminant = getDiscriminant(); if (discriminant < 0) { return 0; } return (-b + Math.sqrt(discriminant)) / (2 * a); } public double getRoot2() { double discriminant = getDiscriminant(); if (discriminant < 0) { return 0; } return (-b - Math.sqrt(discriminant)) / (2 * a); }
我答题所给的代码的时间和空间复杂度都是o(1)
对度量进行分析:
1. 基本代码度量
类级度量
类数量: 2 (Main 和 QuadraticEquation)
方法数量:
- Main类: 1 (main)
- QuadraticEquation类: 8 (构造方法 + 7个公共方法)
2. 复杂度度量
圈复杂度(Cyclomatic Complexity)
main()方法: 4 (3个if分支 + 1)
QuadraticEquation类方法: 都是1或2 (非常简单的逻辑)
3. 面向对象度量
内聚性(Cohesion)
QuadraticEquation类具有很高的功能内聚性:
- 所有方法都围绕二次方程求解这一单一职责
- 没有无关的方法或属性
耦合度(Coupling)
耦合度很低:
- Main类仅依赖于QuadraticEquation类
- QuadraticEquation类不依赖任何其他类
4. 继承与多态度量
继承深度: 0
子类数量: 0
方法重写: 0
- 2. 单部电梯调度程序
题目要求:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
关键代码:
Elevator类的checkStop方法
用于检测当前楼层是否需要停止
private boolean checkStop() { // 检查内部请求 Integer nextInner = innerRequests.peek(); // 内部请求的队头 OuterRequest nextOuter = outerRequests.peek(); // 外部请求的队头 if (!innerRequests.isEmpty() && currentFloor == nextInner) { System.out.println("Current Floor: " + currentFloor + " Direction: " + direction); return true; } if (!outerRequests.isEmpty()&& currentFloor == nextOuter.floor) { if((direction == Direction.UP && nextOuter.direction == Direction.UP) || (direction == Direction.DOWN && nextOuter.direction == Direction.DOWN)) { System.out.println("Current Floor: " + currentFloor + " Direction: " + direction); return true; } else if(!innerRequests.isEmpty()&&nextInner<currentFloor){ System.out.println("Current Floor: " + currentFloor + " Direction: " + direction); direction=nextOuter.direction; return true; } } return false; } processRequests方法: private void processRequests() { // 处理内部请求 if (!innerRequests.isEmpty() && currentFloor == innerRequests.peek()) { innerRequests.poll(); } if (!outerRequests.isEmpty()) { OuterRequest firstRequest = outerRequests.peek(); // 获取队首元素但不移除 boolean isMatch = (firstRequest.floor == currentFloor) && ((direction == Direction.UP && firstRequest.direction == Direction.UP) || (direction == Direction.DOWN && firstRequest.direction == Direction.DOWN)); // isMatch 表示第一个元素是否匹配 if (isMatch){ outerRequests.poll(); } } }
processRequests() 在电梯停靠后处理内部请求和外部请求
语句if (!innerRequests.isEmpty() && currentFloor == innerRequests.peek()) {
innerRequests.poll();
}
检查内部请求队列不为空和当前楼层等于队列中的第一个请求楼层
如果为真则从队列中移除该请求(使用poll())
语句if (!outerRequests.isEmpty()) {
OuterRequest firstRequest = outerRequests.peek(); // 查看但不移除
boolean isMatch = (firstRequest.floor == currentFloor)
&& ((direction == Direction.UP && firstRequest.direction == Direction.UP)
|| (direction == Direction.DOWN && firstRequest.direction == Direction.DOWN));
if (isMatch){
outerRequests.poll();
}
}
这段代码检查如果外部请求队列不为空,当前楼层等于第一个外部请求的楼层,电梯当前方向与外部请求方向一致,如果为真则当条件满足时从队列中移除该请求
Run方法:
public void run() { while (!innerRequests.isEmpty() || !outerRequests.isEmpty()) { //里外队列都不为空 if (direction == Direction.IDLE) { determineDirection(); //更新状态 if (direction == Direction.IDLE) { break; // 没有请求 } } if (direction == Direction.UP) { currentFloor++; boolean needStop = checkStop(); if(!needStop){ System.out.println("Current Floor: " + currentFloor + " Direction: " + direction); } if (needStop) { openDoor(); processRequests(); closeDoor(); } } if (direction == Direction.DOWN) { currentFloor--; boolean needStop = checkStop(); if(!needStop){ System.out.println("Current Floor: " + currentFloor + " Direction: " + direction); } if (needStop) { openDoor(); processRequests(); closeDoor(); } } if (!hasRequestsInCurrentDirection()) { direction = Direction.IDLE; } } }
该方法只要内部请求队列或外部请求队列任一不为空就继续执行,两个队列都为空时结束运行
对代码进行分析:

Project Name Test
Checkpoint Name Baseline
File Name Main.java
Lines 286
Statements 124
Percent Branch Statements 22.6
Method Call Statements 50
Percent Lines with Comments 12.9
Classes and Interfaces 4
Methods per Class 3.25
Average Statements per Method 8.23
Line Number of Most Complex Method 72
Name of Most Complex Method Elevator.run()
Maximum Complexity 11
Line Number of Deepest Block 26
Maximum Block Depth 5
Average Block Depth 1.88
Average Complexity 3.67
--------------------------------------------------------------------------------------------
Most Complex Methods in 3 Class(es): Complexity, Statements, Max Depth, Calls
Elevator.addInnerRequest() 1, 1, 2, 1
Elevator.addOuterRequest() 1, 2, 2, 2
Elevator.Elevator() 1, 6, 2, 0
Elevator.run() 11, 23, 4, 14
Main.main() 7, 19, 5, 15
OuterRequest.OuterRequest() 1, 2, 2, 0
--------------------------------------------------------------------------------------------
Block Depth Statements
0 23
1 30
2 34
3 19
4 12
5 6
6 0
--------------------------------------------------------------------------------------------
-
整体复杂度偏高
-
最复杂的方法是
Elevator.run(),复杂度高达11,包含23条语句和14个方法调用,控制流嵌套深度达4层 -
主入口方法
Main.main()复杂度7也偏高,包含19条语句和15个方法调用 -
平均方法复杂度3.67
-
-
控制流问题突出
-
最大块深度达到5层,平均块深度1.88
-
深度为3-5的嵌套块共包含37条语句,说明存在多层条件/循环嵌套
-
Elevator.run()和Main.main()贡献了主要的深度控制流
-
-
代码结构特征
-
286行代码包含4个类,平均每个类3.25个方法
-
方法平均长度8.23条语句
-
分支语句占比22.6%表明业务逻辑判断密集
-
注释覆盖率12.9%低于推荐值
-
题目集6的前两题都较为基础,第3题是对题目集5单部电梯调度程序的迭代,在题目集5中的电梯类职责过多,为了解决这个问题将电梯类中分离一些方法给乘客请求类、队列类以及控制类,类图参考:

题目集7的单部电梯调度算法则是在题目集6单部电梯调度算法的基础上添加了乘客类,其类图参考:

三.踩坑心得
题目集5的单部电梯调度程序我并未通过测试点,当我输入:
用测试用例
1
20
<3>
<3,UP>
<3,DOWN>
End
输出的时候会陷入死循环,反复在2层3层循环,出现这个问题的原因是电梯运行的判定逻辑有问题,目前并未解决这个问题,怀疑是代码中checkStop()和determineDirection()方法判定的顺序有问题导致在3层后有同样的内部和外部请求,它先更改方向向下回到1层但是请求队列并不为空,在到达2层后又被要求前往3层
在迭代中也未解决这个问题,但在题目集6单部电梯调度算法中成功解决了电梯类职责过多的问题,其结构如下:



三.总结
这三次编程作业带我完整走过了从基础恢复到面向对象思维建立的成长过程。最初用单一类实现电梯调度时遇到了逻辑混乱、难以维护的问题,后来通过拆分职责到不同类(电梯类、请求类、控制类等),代码变得清晰易扩展。我深刻体会到"一个类只做一件事"的重要性,也学会了如何设计低耦合的类结构。虽然中间遇到过电梯运行死循环这样的bug,但通过系统性调试和逐步优化,最终实现了功能完善且结构合理的电梯调度系统,这种将复杂问题分解到不同类的思考方式让我受益匪浅。

浙公网安备 33010602011771号