面向对象设计与构造——Blog-1
面向对象设计与构造——第一次题目集练习总结
7-1:身份证校验位计算
题目要求
根据身份证前17位数字,按照ISO 7064:1983标准的MOD 11-2校验算法计算校验位。计算步骤:
1、将前17位数字分别乘以系数:[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
2、将乘积相加得到总和
3、将总和除以11取余数
4、根据余数对应的规则(0->1, 1->0, 2->X, 3->9, 4->8, 5->7, 6->6, 7->5, 8->4, 9->3, 10->2)得到最后一位校验位
实现思路
1、读取输入的17位数字字符串
2、检查输入是否合法(长度17,且都是数字)
3、将字符串转为字符数组,然后转换为整数数组
5、计算余数:总和 % 11
6、根据余数映射得到校验位(注意余数为2时,校验位为'X')
代码结构

复杂度分析
1、时间复杂度:O(1),因为输入固定17位,循环次数固定
2、空间复杂度:O(1),使用了固定大小的数组
测试方法
1、测试用例:使用已知的身份证号前17位,验证输出校验位是否正确
2、边界测试:输入全0,输入全9,以及随机生成的17位数字
3、题目要求:
(1)输入格式:
从键盘输入17位数字。(不考虑身份证号码内部正确性逻辑)
(2)输出格式:
如果输入不是17位数字,则输出“Wrong Format”
如果输入正确,输出完整的18位身份证号。
输入样例: 在这里给出一组输入。例如: 36010120081024324
输出样例: 在这里给出相应的输出。例如: 360101200810243240
代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB
栈限制 8192 KB
Bug分析
1、输入验证:如果输入不是17位数字,需要处理。在代码中已经做了检查。
2、注意:题目要求输入是17位数字,所以不需要考虑非数字情况(但代码中已用正则检查)。
7-2:一元二次方程求解(类设计)
题目要求
定义一个代表一元二次方程
的类QuadraticEquation,其属性为三个系数a、b、c(均为私有属性),类中定义的方法参考main方法中的代码。main方法源码:

实现思路
1、设计一个类QuadraticEquation,包含三个私有属性a、b、c(double类型):
(1)getA(), getB(), getC():获取系数
(2)getDiscriminant():返回判别式(b²-4ac)
(3)getRoot1():返回第一个根(判别式非负时,(-b+sqrt(判别式))/(2a))
(4)getRoot2():返回第二个根(判别式非负时,(-b-sqrt(判别式))/(2a))
2、在类中定义私有属性a, b, c
(1)编写构造方法初始化这三个属性
(2)编写getter方法
(3)实现getDiscriminant(),返回bb-4a*c
(4)实现getRoot1()和getRoot2(),注意当判别式为负时,返回NaN(但题目中main方法已经判断了,所以这里可以不用处理负数的开方,因为调用这两个方法时已经确保判别式非负)
代码结构

复杂度分析
1、时间复杂度:O(1),所有操作都是常数时间
2、空间复杂度:O(1)
测试方法
1、测试用例:使用已知的一元二次方程,验证判别式和根的计算是否正确
2、特殊情况:a=0(在main中已处理),判别式为0(两个根相等),判别式为负(无实根)
3、题目要求:
(1)输入格式:
在一行中输入a、b、c的值,可以用一个或多个空格或回车符分开。
(2)输出格式:
当输入非法时,输出“Wrong Format”
当有一个实根时,输出(2行):
a=值,b=值,c=值:
The root is 值(保留两位小数)
当有两个实根时,输出(2行):
a=值,b=值,c=值:
The roots are 值1 and 值2(均保留两位小数)
输入样例1:
在这里给出一组输入。例如:
1 84 -6653
输出样例1:
在这里给出相应的输出。例如:
a=1.0,b=84.0,c=-6653.0:
The roots are 49.74 and -133.74
输入样例2:
在这里给出一组输入。例如:
1.00 -2.000 1
输出样例2:
在这里给出相应的输出。例如:
a=1.0,b=-2.0,c=1.0:
The root is 1.00
代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB
栈限制 8192 KB
Bug分析
注意:在getRoot1和getRoot2中,我们检查了判别式是否为负,返回NaN。虽然main方法中已经判断,但类的方法应该独立保证健壮性。
7-3:验证码校验(正则表达式)
一、 题目要求
接受给定的字符串,判断该字符串是否属于验证码。验证码是由四位数字或者字母(包含大小写)组成的字符串。
实现思路
验证码是由四位数字或者字母(包含大小写)组成的字符串。编写程序判断输入的字符串是否属于验证码。
使用正则表达式:长度为4,且每个字符是数字或大小写字母。正则表达式:[0-9a-zA-Z]{4}
代码结构

复杂度分析
(1)时间复杂度:O(1),正则匹配固定长度字符串
(2)空间复杂度:O(1)
测试方法
1、测试用例:输入4位数字、4位字母(大小写混合)、不足4位、超过4位、包含非数字字母字符等
2、题目要求:
(1)输入格式:
在一行内输入一个字符串。
(2)输出格式:
判断该字符串是否符合验证码规则,若是验证码,输出字符串是验证码,否则输出字符串不是验证码。
输入样例1:
在这里给出一组输入。例如:
123A
输出样例1:
在这里给出相应的输出。例如:
123A属于验证码
输入样例2:
在这里给出一组输入。例如:
12?AD
输出样例2:
在这里给出相应的输出。例如:
12?AD不属于验证码
代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB
栈限制 8192 K
7-4:QQ号校验(正则表达式)
题目要求
校验QQ号是否合格:
1、5-15位
2、0不能开头
3、必须都是数字
实现思路
使用正则表达式:首位为1-9,后面跟着4-14位数字。正则表达式:[1-9][0-9]{4,14}
代码结构

复杂度分析
(1)时间复杂度:O(n),n为字符串长度,但长度有限(最大15),可视为O(1)
(2)空间复杂度:O(1)
测试方法
1、测试用例:首位为0、长度小于5、长度大于15、包含非数字字符、正确QQ号等
2、题目要求:
(1)输入格式:
在一行中输入一个字符串。
(2)输出格式:
如果合格,输出:“你输入的QQ号验证成功”;
否则,输出:“你输入的QQ号验证失败”。
输入样例1:
在这里给出一组输入。例如:
1234567890
输出样例1:
在这里给出相应的输出。例如:
你输入的QQ号验证成功
输入样例2:
在这里给出一组输入。例如:
123456789O
输出样例2:
在这里给出相应的输出。例如:
你输入的QQ号验证失败
代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB
栈限制 8192 KB
7-5:单部电梯调度程序
题目要求
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
实现思路
1、设计一个电梯类,模拟电梯调度。电梯运行规则:
- 默认停在1层,状态静止
- 有请求时开始移动
- 移动方向:优先处理同方向请求,再处理反方向请求
- 每次移动一层,检查是否有停靠请求(内部请求或外部同方向请求)
- 请求串行处理(一次只处理一个请求,处理完再响应下一个)
2、设计电梯类(Elevator),包含属性:
- 当前楼层(currentFloor)
- 运行方向(direction):枚举(UP, DOWN, STOP)
- 状态(state):枚举(STOPPED, MOVING, OPENING, CLOSING)
- 内部请求队列(内部按钮按下的目标楼层)
- 外部请求队列(分上行和下行,用两个队列或两个集合存储,但注意外部请求有方向)
- 最大楼层和最小楼层(默认为1,最大需要指定)
3、电梯行为:
- 添加内部请求(目标楼层)
- 添加外部请求(楼层和方向)
- 运行:根据当前状态和请求队列决定下一步动作
4、调度算法:
- 当电梯静止时,收到请求,根据请求楼层与当前位置决定方向
- 移动过程中,检查当前楼层是否有请求(内部请求或外部同方向请求),有则停靠
- 停靠时开门,处理该楼层请求(从内部请求和外部请求队列中移除),然后关门
- 当当前方向没有请求时,检查反方向请求,如果有则改变方向,否则停止
注意:请求串行处理,即每次只添加一个请求,处理完后再添加下一个请求。
代码结构
点击查看代码
import java.util.*;
enum Direction {
UP, DOWN, STOP
}
enum State {
STOPPED, MOVING, OPENING, CLOSING
}
class Request {
int floor;
Direction direction; // 对于内部请求,direction为null或忽略;外部请求需要方向
boolean isInternal; // 区分内部请求和外部请求
public Request(int floor, Direction direction, boolean isInternal) {
this.floor = floor;
this.direction = direction;
this.isInternal = isInternal;
}
}
class Elevator {
private int currentFloor;
private Direction direction;
private State state;
private int minFloor;
private int maxFloor;
private List<Integer> internalRequests; // 内部请求的目标楼层
private List<Request> externalRequests; // 外部请求(包括楼层和方向)
public Elevator(int minFloor, int maxFloor) {
this.minFloor = minFloor;
this.maxFloor = maxFloor;
currentFloor = 1;
direction = Direction.STOP;
state = State.STOPPED;
internalRequests = new ArrayList<>();
externalRequests = new ArrayList<>();
}
// 添加内部请求
public void addInternalRequest(int floor) {
if (floor < minFloor || floor > maxFloor) {
System.out.println("无效楼层");
return;
}
if (!internalRequests.contains(floor)) {
internalRequests.add(floor);
}
// 如果有请求,且电梯停止,则启动
if (state == State.STOPPED) {
// 根据请求楼层与当前位置决定方向
if (floor > currentFloor) {
direction = Direction.UP;
} else if (floor < currentFloor) {
direction = Direction.DOWN;
}
state = State.MOVING;
}
}
// 添加外部请求
public void addExternalRequest(int floor, Direction dir) {
if (floor < minFloor || floor > maxFloor) {
System.out.println("无效楼层");
return;
}
Request request = new Request(floor, dir, false);
if (!externalRequests.contains(request)) {
externalRequests.add(request);
}
// 同样,如果电梯停止,则启动
if (state == State.STOPPED) {
if (floor > currentFloor) {
direction = Direction.UP;
} else if (floor < currentFloor) {
direction = Direction.DOWN;
}
state = State.MOVING;
}
}
// 电梯运行一步(模拟时间推进)
public void step() {
if (state == State.OPENING) {
// 开门后下一步是关门
state = State.CLOSING;
} else if (state == State.CLOSING) {
// 关门后,检查下一步是移动还是停止
state = State.MOVING;
} else if (state == State.MOVING) {
// 移动一层
if (direction == Direction.UP) {
currentFloor++;
} else if (direction == Direction.DOWN) {
currentFloor--;
}
// 检查当前楼层是否有请求
checkRequests();
} else if (state == State.STOPPED) {
// 停止状态,有请求时启动
if (!internalRequests.isEmpty() || !externalRequests.isEmpty()) {
// 确定方向
determineDirection();
if (direction != Direction.STOP) {
state = State.MOVING;
}
}
}
}
private void checkRequests() {
// 检查当前楼层是否有内部请求
if (internalRequests.contains(currentFloor)) {
// 停靠
openDoor();
internalRequests.remove(Integer.valueOf(currentFloor));
return;
}
// 检查外部请求:当前楼层是否有与电梯运行方向一致的外部请求
for (Request req : externalRequests) {
if (req.floor == currentFloor &&
(req.direction == direction ||
(direction == Direction.UP && req.direction == Direction.UP) ||
(direction == Direction.DOWN && req.direction == Direction.DOWN))) {
openDoor();
externalRequests.remove(req);
return;
}
}
// 如果没有停靠,则继续移动
}
private void openDoor() {
state = State.OPENING;
System.out.println("电梯在" + currentFloor + "层开门");
}
private void determineDirection() {
// 如果当前方向有请求,则保持方向
if (direction == Direction.UP) {
if (hasRequestAbove()) {
return;
}
if (hasRequestBelow()) {
direction = Direction.DOWN;
} else {
direction = Direction.STOP;
state = State.STOPPED;
}
} else if (direction == Direction.DOWN) {
if (hasRequestBelow()) {
return;
}
if (hasRequestAbove()) {
direction = Direction.UP;
} else {
direction = Direction.STOP;
state = State.STOPPED;
}
} else { // STOP
// 当前停止,看哪个方向有请求
if (hasRequestAbove()) {
direction = Direction.UP;
} else if (hasRequestBelow()) {
direction = Direction.DOWN;
}
}
}
private boolean hasRequestAbove() {
// 检查当前楼层以上是否有请求(内部请求或外部请求)
for (int floor : internalRequests) {
if (floor > currentFloor) {
return true;
}
}
for (Request req : externalRequests) {
if (req.floor > currentFloor) {
return true;
}
}
return false;
}
private boolean hasRequestBelow() {
// 检查当前楼层以下是否有请求
for (int floor : internalRequests) {
if (floor < currentFloor) {
return true;
}
}
for (Request req : externalRequests) {
if (req.floor < currentFloor) {
return true;
}
}
return false;
}
}
public class ElevatorSimulation {
public static void main(String[] args) {
Elevator elevator = new Elevator(1, 20); // 假设楼层1-20
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String command = scanner.nextLine();
// 解析命令,例如:"内部 5" 或 "外部 3 UP"
String[] parts = command.split(" ");
if (parts[0].equals("内部")) {
int floor = Integer.parseInt(parts[1]);
elevator.addInternalRequest(floor);
} else if (parts[0].equals("外部")) {
int floor = Integer.parseInt(parts[1]);
Direction dir = Direction.valueOf(parts[2]);
elevator.addExternalRequest(floor, dir);
}
// 模拟电梯运行一步
elevator.step();
}
}
}
复杂度分析
1、时间复杂度:每次step操作,检查请求队列,最坏O(n),n为请求数量
2、空间复杂度:O(n),存储请求队列
测试方法
1、测试用例
测试用例1:内部请求,从1层到5层,观察电梯是否逐层上升并停靠
测试用例2:在电梯上升过程中,在3层添加外部上行请求,观察是否停靠
测试用例3:电梯在5层,添加3层的外部下行请求,电梯应先上升到5层,然后下降到3层
测试用例4:多个请求,验证是否优先同方向
2、题目要求


输入样例:
在这里给出一组输入。例如:
1
20
❤️,UP>
<5>
<6,DOWN>
<7>
<3>
end
输出样例:
在这里给出相应的输出。例如:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Open Door # Floor 6
Close Door
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
https://images.ptausercontent.com/b980fd0a-0919-48a1-bc7d-c4748c4e1ea5.pdf
代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB
栈限制 8192 KB
Bug分析
1、在检查外部请求时,需要同时考虑请求的方向和电梯当前方向是否一致
2、当电梯改变方向时,需要重新确定方向,避免遗漏请求
注意:在添加请求时,如果电梯处于停止状态,需要启动
面向对象设计与构造——第二次题目集练习总结
7-1:点与线(类设计)
题目要求
设计一个类表示平面直角坐标系上的点Point,私有属性分别为横坐标x与纵坐标y,数据类型均为实型数。除构造方法以及属性的getter与setter方法外,定义一个用于显示信息的方法display(),用来输出该坐标点的坐标信息,格式为(x,y),数值保留两位小数。坐标点的取值范围设定为(0,200],若输入有误,系统则直接输出Wrong Format。
设计一个类表示平面直角坐标系上的线Line,私有属性除了标识线段两端的点point1、point2外,还有一个字符串类型的color,用于表示该线段的颜色。除构造方法以及属性的getter与setter方法外,定义一个用于计算该线段长度的方法getDistance(),还有一个用于显示信息的方法display(),用来输出线段的相关信息。

类设计分析
1、Point类设计
根据UML类图,Point类包含以下成员:
属性:
- x : double- 横坐标
- y : double- 纵坐标
方法:
- 构造方法:Point()(无参)和Point(double x, double y)(带参)
- getter/setter方法:getX(), setX(double x), getY(), setY(double y)
- display()- 显示坐标信息
2、Line类设计
根据UML类图,Line类包含以下成员:
属性:
- point1 : Point- 线段起点
- point2 : Point- 线段终点
- color : String- 线段颜色
3、方法:
- 构造方法:Line(Point p1, Point p2, String color)和Line(Point point1, Point point2)
- getter/setter方法
- getDistance() : double- 计算线段长度
- display() : void- 显示线段信息
代码实现
点击查看代码
import java.util.Scanner;
// Point类:表示平面直角坐标系上的点
class Point {
private double x;
private double y;
// 无参构造方法
public Point() {
this.x = 0;
this.y = 0;
}
// 带参构造方法
public Point(double x, double y) {
// 检查坐标范围
if (x <= 0 || x > 200 || y <= 0 || y > 200) {
System.out.println("Wrong Format");
System.exit(0);
}
this.x = x;
this.y = y;
}
// getter和setter方法
public double getX() {
return x;
}
public void setX(double x) {
// 检查坐标范围
if (x <= 0 || x > 200) {
System.out.println("Wrong Format");
System.exit(0);
}
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
// 检查坐标范围
if (y <= 0 || y > 200) {
System.out.println("Wrong Format");
System.exit(0);
}
this.y = y;
}
// 显示坐标信息
public void display() {
System.out.printf("(%.2f,%.2f)\n", x, y);
}
}
// Line类:表示平面直角坐标系上的线段
class Line {
private Point point1;
private Point point2;
private String color;
// 构造方法1:包含颜色参数
public Line(Point p1, Point p2, String color) {
this.point1 = p1;
this.point2 = p2;
this.color = color;
}
// 构造方法2:不包含颜色参数(默认颜色)
public Line(Point point1, Point point2) {
this.point1 = point1;
this.point2 = point2;
this.color = "black"; // 默认颜色
}
// getter和setter方法
public Point getPoint1() {
return point1;
}
public void setPoint1(Point point1) {
this.point1 = point1;
}
public Point getPoint2() {
return point2;
}
public void setPoint2(Point point2) {
this.point2 = point2;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
// 计算线段长度
public double getDistance() {
double dx = point2.getX() - point1.getX();
double dy = point2.getY() - point1.getY();
return Math.sqrt(dx * dx + dy * dy);
}
// 显示线段信息
public void display() {
System.out.println("The line's color is:" + color);
System.out.println("The line's begin point's Coordinate is:");
point1.display();
System.out.println("The line's end point's Coordinate is:");
point2.display();
System.out.printf("The line's length is:%.2f\n", getDistance());
}
}
// 主类
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取第一个点的坐标
double x1 = scanner.nextDouble();
double y1 = scanner.nextDouble();
// 读取第二个点的坐标
double x2 = scanner.nextDouble();
double y2 = scanner.nextDouble();
// 读取颜色
String color = scanner.next();
// 创建点对象
Point p1 = new Point(x1, y1);
Point p2 = new Point(x2, y2);
// 创建线段对象
Line line = new Line(p1, p2, color);
// 显示线段信息
line.display();
scanner.close();
}
}
复杂度分析
1、时间复杂度
- Point类:所有方法都是O(1)时间复杂度
- Line类:所有方法都是O(1)时间复杂度,包括getDistance()方法
- Main类:O(1),只有简单的输入输出操作
2、空间复杂度
- 程序使用了固定数量的变量,空间复杂度为O(1)
测试方法
1、测试用例设计
正常情况测试:
- 输入:10.5 20.3 30.7 40.9 red
-
- 期望输出:正确显示线段信息,长度计算正确
- 输入:1.0 1.0 200.0 200.0 blue
-
- 期望输出:正确显示最大范围内的线段信息
边界情况测试:
- 输入:0.1 0.1 200.0 200.0 green
-
- 期望输出:正确显示最小边界值的线段信息
- 输入:200.0 200.0 200.0 200.0 yellow
-
- 期望输出:长度为0的线段信息
异常情况测试:
- 输入:0.0 10.0 20.0 30.0 red
-
- 期望输出:Wrong Format(x坐标超出范围)
- 输入:10.0 0.0 20.0 30.0 blue
-
- 期望输出:Wrong Format(y坐标超出范围)
- 输入:10.0 20.0 201.0 30.0 green
-
- 期望输出:Wrong Format(x坐标超出范围)
2、测试结果验证
- 对于正常情况,验证输出格式是否符合要求
- 对于边界情况,验证程序是否能正确处理边界值
- 对于异常情况,验证程序是否能正确检测并报错
Bug分析
1、潜在问题
- 浮点数精度问题:在计算线段长度时,使用Math.sqrt()可能存在浮点数精度误差
- 解决方案:使用String.format("%.2f", data)格式化输出,控制精度
- 坐标范围检查:在setter方法中检查坐标范围,但构造方法中也需要检查
- 当前实现:已在构造方法和setter方法中都进行了范围检查
2、已解决的问题
- 输入验证:在Point类的构造方法和setter方法中都进行了坐标范围验证
- 默认值处理:Line类提供了带默认颜色的构造方法
- 输出格式:严格按照题目要求的格式进行输出
7-2:汽车风挡玻璃雨刷问题(类设计)
题目要求
设计一个雨刷控制系统,包含控制杆类、刻度盘类、雨刷类和控制类。雨刷的摆动速度由控制杆和刻度盘两种元件决定:
控制杆有四个档位:停止、间歇、低速、高速
刻度盘有五种刻度:1、2、3、4、5
雨刷速度根据档位和刻度组合决定(具体对应关系见题目表格)
输入为一系列操作指令(1-4),分别代表:
1:控制杆升档
2:控制杆降档
3:刻度盘升档
4:刻度盘降档
要求类设计必须符合单一职责原则(SRP)。

类设计分析
- 控制杆类(Lever)
职责:管理控制杆的档位状态和档位变化
属性:
- position:当前档位(0-停止,1-间歇,2-低速,3-高速)
方法: - upPos():控制杆升档
- downPos():控制杆降档
- getPosition():获取当前档位
- 刻度盘类(Dial)
职责:管理刻度盘的刻度状态和刻度变化
属性:
- position:当前刻度(1-5)
方法: - upPos():刻度盘升档
- downPos():刻度盘降档
- getPosition():获取当前刻度
- 雨刷类(Brush)
职责:存储雨刷的当前速度
属性:
- speed:当前雨刷速度
方法: - setSpeed(int speed):设置雨刷速度
- getSpeed():获取雨刷速度
- 控制类(Agent)
职责:根据控制杆和刻度盘的状态计算并设置雨刷速度
属性:
- lever:控制杆对象
- dial:刻度盘对象
- brush:雨刷对象
方法: - dealSpeed():根据当前状态计算雨刷速度
- show():输出当前雨刷速度
代码实现
点击查看代码
import java.util.*;
// 控制杆类
class Lever {
private int position; // 0:停止, 1:间歇, 2:低速, 3:高速
public Lever(int position) {
this.position = position;
}
public void upPos() {
if (position < 3) {
position++;
}
}
public void downPos() {
if (position > 0) {
position--;
}
}
public int getPosition() {
return position;
}
}
// 刻度盘类
class Dial {
private int position; // 1-5
public Dial(int position) {
this.position = position;
}
public void upPos() {
if (position < 5) {
position++;
}
}
public void downPos() {
if (position > 1) {
position--;
}
}
public int getPosition() {
return position;
}
}
// 雨刷类
class Brush {
private int speed;
public Brush(int speed) {
this.speed = speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
}
// 控制类
class Agent {
private Lever lever;
private Dial dial;
private Brush brush;
public Agent(Lever lever, Dial dial, Brush brush) {
this.lever = lever;
this.dial = dial;
this.brush = brush;
}
public Lever getLever() {
return lever;
}
public Dial getDial() {
return dial;
}
public void dealSpeed() {
int leverPos = lever.getPosition();
int dialPos = dial.getPosition();
int speed = 0;
// 根据表格计算雨刷速度
switch (leverPos) {
case 0: // 停止
speed = 0;
break;
case 1: // 间歇
switch (dialPos) {
case 1: speed = 4; break;
case 2: speed = 6; break;
case 3: speed = 12; break;
case 4: speed = 18; break;
case 5: speed = 24; break;
}
break;
case 2: // 低速
speed = 30;
break;
case 3: // 高速
speed = 60;
break;
}
brush.setSpeed(speed);
}
public void show() {
System.out.print(brush.getSpeed() + " ");
}
}
// 主类(题目已给出)
public class Main {
public static void main(String[] args) {
Lever lever = new Lever(1);//控制杆
Dial dial = new Dial(1);//刻度盘
Brush brush = new Brush(0);//雨刷
Agent agent = new Agent(lever,dial,brush);//控制类
Scanner input = new Scanner(System.in);
int choice = 0;
List<Integer> list = new ArrayList<Integer>();
choice = input.nextInt();
//一次性接受用户输入数据
while(choice >= 1 && choice <= 4) {
list.add(choice);
choice = input.nextInt();
}
Iterator<Integer> itr = list.iterator();
int operator = 0;
//对用户输入进行遍历
while(itr.hasNext()) {
operator = itr.next();
switch(operator) {
case 1:agent.getLever().upPos();break;//控制杆升档
case 2:agent.getLever().downPos();break;//控制杆降档
case 3:agent.getDial().upPos();break;//刻度盘升刻度
case 4:agent.getDial().downPos();break;//刻度盘降刻度
}
agent.dealSpeed();//处理雨刷速度
agent.show();//输出结果
}
}
}
复杂度分析
1、时间复杂度
- Lever类:upPos()和downPos()方法都是O(1)时间复杂度
- Dial类:upPos()和downPos()方法都是O(1)时间复杂度
- Brush类:所有方法都是O(1)时间复杂度
- Agent类:dealSpeed()方法使用switch语句,O(1)时间复杂度
- Main类:循环处理输入,O(n)时间复杂度,n为输入操作的数量
2、空间复杂度
- 程序使用了固定数量的对象和变量,空间复杂度为O(1)
测试方法
1、正常操作测试:
- 输入:1 3 3 4 2(控制杆升档->刻度盘升档->刻度盘升档->刻度盘降档->控制杆降档)
- 期望输出:4 6 12 6 0
2、边界情况测试:
控制杆在最高档继续升档:1 1 1 1 - 期望输出:4 30 60 60(最后一个操作不会超过最高档)
控制杆在最低档继续降档:2 2 2 - 期望输出:0 0 0(不会低于最低档)
刻度盘在最高刻度继续升档:3 3 3 3 3 - 期望输出:6 12 18 24 24(最后一个操作不会超过最高刻度)
刻度盘在最低刻度继续降档:4 4 4 - 期望输出:4 4 4(不会低于最低刻度)
3、组合操作测试: - 输入样例:1 2 1 3 3 3 3 3 3 4 4 4 4 1 1 1 2 2 2 2
- 期望输出:4 0 4 6 12 18 24 24 24 18 12 6 4 30 60 60 30 4 0 0
Bug分析
1、潜在问题及解决方案
状态同步问题:控制杆和刻度盘的状态变化需要及时反映到雨刷速度上
- 解决方案:每次操作后立即调用dealSpeed()方法更新速度
边界值处理:档位和刻度不能超出有效范围 - 解决方案:在upPos()和downPos()方法中添加边界检查
初始状态设置:题目要求控制杆初始为1(间歇),刻度盘初始为1 - 解决方案:在构造函数中正确设置初始值
2、已解决的问题
- 单一职责原则:每个类都有明确的单一职责
- 状态管理:各类正确管理自身状态,不越界
- 速度计算:正确根据表格映射关系计算雨刷速度
7-3:单部电梯调度程序(类设计)
题目要求
对之前的单部电梯调度程序进行迭代性设计,解决电梯类职责过多的问题,遵循单一职责原则(SRP)。必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体要求如下:

1、处理无效楼层请求(高于最高楼层或低于最低楼层)
2、过滤连续的相同请求(如<3><3><3>过滤为<3>)
3、类设计必须符合单一职责原则
类设计分析
根据提供的UML类图,我们设计以下类结构:
- 请求类(Request)
职责:封装乘客请求的基本信息
属性:
- floor:请求楼层
- direction:请求方向(UP/DOWN,仅外部请求有)
- type:请求类型(INTERNAL/EXTERNAL)
方法: - 构造方法
- getter方法
- equals():判断请求是否相同
-
内部请求类(InternalRequest)
职责:继承自Request,表示电梯内部按钮请求 -
外部请求类(ExternalRequest)
职责:继承自Request,表示电梯外部按钮请求 -
请求队列类(RequestQueue)
职责:管理请求队列,处理重复请求过滤
属性:
- internalQueue:内部请求队列
- externalUpQueue:外部上行请求队列
- externalDownQueue:外部下行请求队列
方法:
addRequest(Request request):添加请求(自动过滤重复)
getNextRequest():获取下一个待处理请求
hasRequestAtFloor(int floor, Direction direction):检查某楼层是否有指定方向请求
removeRequestAtFloor(int floor, Direction direction):移除某楼层的指定方向请求
- 电梯类(Elevator)
职责:管理电梯的物理状态和基本操作
属性:
- currentFloor:当前楼层
- direction:当前方向
- state:当前状态(MOVING, STOPPED, OPENING, CLOSING)
- minFloor:最小楼层
- maxFloor:最大楼层
方法: - move():电梯移动一层
- openDoor():开门
- closeDoor():关门
- changeDirection():改变方向
- 控制类(Controller)
职责:负责电梯的调度逻辑
属性:
- levator:电梯对象
- requestQueue:请求队列对象
方法: - processRequests():处理所有请求
- shouldStop():判断当前楼层是否需要停靠
- determineNextDirection():确定下一步移动方向
代码实现
点击查看代码
import java.util.*;
// 方向枚举
enum Direction {
UP, DOWN, STOP
}
// 电梯状态枚举
enum ElevatorState {
MOVING, STOPPED, OPENING, CLOSING
}
// 请求类型枚举
enum RequestType {
INTERNAL, EXTERNAL
}
// 请求基类
abstract class Request {
protected int floor;
protected Direction direction;
protected RequestType type;
public Request(int floor, Direction direction, RequestType type) {
this.floor = floor;
this.direction = direction;
this.type = type;
}
public int getFloor() {
return floor;
}
public Direction getDirection() {
return direction;
}
public RequestType getType() {
return type;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Request request = (Request) obj;
return floor == request.floor &&
direction == request.direction &&
type == request.type;
}
@Override
public int hashCode() {
return Objects.hash(floor, direction, type);
}
}
// 内部请求类
class InternalRequest extends Request {
public InternalRequest(int floor) {
super(floor, null, RequestType.INTERNAL);
}
}
// 外部请求类
class ExternalRequest extends Request {
public ExternalRequest(int floor, Direction direction) {
super(floor, direction, RequestType.EXTERNAL);
}
}
// 请求队列类
class RequestQueue {
private Set<Request> allRequests; // 用于去重
private Queue<Request> internalQueue;
private Queue<Request> externalUpQueue;
private Queue<Request> externalDownQueue;
private int minFloor;
private int maxFloor;
public RequestQueue(int minFloor, int maxFloor) {
this.minFloor = minFloor;
this.maxFloor = maxFloor;
this.allRequests = new HashSet<>();
this.internalQueue = new LinkedList<>();
this.externalUpQueue = new LinkedList<>();
this.externalDownQueue = new LinkedList<>();
}
public boolean addRequest(Request request) {
// 检查楼层是否有效
if (request.getFloor() < minFloor || request.getFloor() > maxFloor) {
return false; // 忽略无效楼层请求
}
// 检查是否重复请求
if (allRequests.contains(request)) {
return false; // 忽略重复请求
}
allRequests.add(request);
// 根据请求类型和方向添加到对应队列
if (request.getType() == RequestType.INTERNAL) {
internalQueue.add(request);
} else {
if (request.getDirection() == Direction.UP) {
externalUpQueue.add(request);
} else {
externalDownQueue.add(request);
}
}
return true;
}
public Request getNextRequest(int currentFloor, Direction currentDirection) {
// 优先处理同方向的请求
if (currentDirection == Direction.UP) {
// 先检查当前楼层以上的内部请求
for (Request req : internalQueue) {
if (req.getFloor() >= currentFloor) {
return req;
}
}
// 再检查当前楼层以上的外部上行请求
for (Request req : externalUpQueue) {
if (req.getFloor() >= currentFloor) {
return req;
}
}
} else if (currentDirection == Direction.DOWN) {
// 先检查当前楼层以下的内部请求
for (Request req : internalQueue) {
if (req.getFloor() <= currentFloor) {
return req;
}
}
// 再检查当前楼层以下的外部下行请求
for (Request req : externalDownQueue) {
if (req.getFloor() <= currentFloor) {
return req;
}
}
}
// 如果没有同方向请求,检查反方向请求
if (!internalQueue.isEmpty()) {
return internalQueue.peek();
}
if (!externalUpQueue.isEmpty()) {
return externalUpQueue.peek();
}
if (!externalDownQueue.isEmpty()) {
return externalDownQueue.peek();
}
return null;
}
public boolean hasRequestAtFloor(int floor, Direction direction) {
// 检查内部请求
for (Request req : internalQueue) {
if (req.getFloor() == floor) {
return true;
}
}
// 检查外部请求
if (direction == Direction.UP) {
for (Request req : externalUpQueue) {
if (req.getFloor() == floor) {
return true;
}
}
} else {
for (Request req : externalDownQueue) {
if (req.getFloor() == floor) {
return true;
}
}
}
return false;
}
public void removeRequestAtFloor(int floor, Direction direction) {
// 移除内部请求
internalQueue.removeIf(req -> req.getFloor() == floor);
// 移除外部请求
if (direction == Direction.UP) {
externalUpQueue.removeIf(req -> req.getFloor() == floor);
} else {
externalDownQueue.removeIf(req -> req.getFloor() == floor);
}
// 从所有请求集合中移除
allRequests.removeIf(req -> req.getFloor() == floor &&
(req.getType() == RequestType.INTERNAL || req.getDirection() == direction));
}
public boolean hasRequests() {
return !internalQueue.isEmpty() || !externalUpQueue.isEmpty() || !externalDownQueue.isEmpty();
}
}
// 电梯类
class Elevator {
private int currentFloor;
private Direction direction;
private ElevatorState state;
private int minFloor;
private int maxFloor;
public Elevator(int minFloor, int maxFloor) {
this.minFloor = minFloor;
this.maxFloor = maxFloor;
this.currentFloor = 1; // 默认停在1层
this.direction = Direction.STOP;
this.state = ElevatorState.STOPPED;
}
public void move() {
if (direction == Direction.UP) {
if (currentFloor < maxFloor) {
currentFloor++;
}
} else if (direction == Direction.DOWN) {
if (currentFloor > minFloor) {
currentFloor--;
}
}
System.out.println("Current Floor: " + currentFloor + " Direction: " + direction);
}
public void openDoor() {
state = ElevatorState.OPENING;
System.out.println("Open Door # Floor " + currentFloor);
}
public void closeDoor() {
state = ElevatorState.CLOSING;
System.out.println("Close Door");
}
public void changeDirection(Direction newDirection) {
this.direction = newDirection;
}
// Getter和Setter方法
public int getCurrentFloor() { return currentFloor; }
public Direction getDirection() { return direction; }
public ElevatorState getState() { return state; }
public void setState(ElevatorState state) { this.state = state; }
public int getMinFloor() { return minFloor; }
public int getMaxFloor() { return maxFloor; }
}
// 控制类
class Controller {
private Elevator elevator;
private RequestQueue requestQueue;
public Controller(int minFloor, int maxFloor) {
this.elevator = new Elevator(minFloor, maxFloor);
this.requestQueue = new RequestQueue(minFloor, maxFloor);
}
public void addRequest(Request request) {
requestQueue.addRequest(request);
}
public void processRequests() {
while (requestQueue.hasRequests()) {
// 确定下一步方向
determineNextDirection();
// 如果没有请求,停止
if (elevator.getDirection() == Direction.STOP) {
break;
}
// 移动电梯
elevator.move();
// 检查是否需要停靠
if (shouldStop()) {
elevator.openDoor();
// 处理该楼层的请求
requestQueue.removeRequestAtFloor(elevator.getCurrentFloor(), elevator.getDirection());
elevator.closeDoor();
}
}
}
private boolean shouldStop() {
return requestQueue.hasRequestAtFloor(elevator.getCurrentFloor(), elevator.getDirection());
}
private void determineNextDirection() {
Request nextRequest = requestQueue.getNextRequest(elevator.getCurrentFloor(), elevator.getDirection());
if (nextRequest == null) {
elevator.changeDirection(Direction.STOP);
return;
}
int targetFloor = nextRequest.getFloor();
if (targetFloor > elevator.getCurrentFloor()) {
elevator.changeDirection(Direction.UP);
} else if (targetFloor < elevator.getCurrentFloor()) {
elevator.changeDirection(Direction.DOWN);
} else {
// 目标楼层就是当前楼层,需要停靠
elevator.changeDirection(Direction.STOP);
}
}
}
// 主程序
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取最小和最大楼层
int minFloor = scanner.nextInt();
int maxFloor = scanner.nextInt();
scanner.nextLine(); // 消耗换行符
Controller controller = new Controller(minFloor, maxFloor);
// 读取请求
while (true) {
String line = scanner.nextLine().trim();
if (line.equalsIgnoreCase("end")) {
break;
}
Request request = parseRequest(line);
if (request != null) {
controller.addRequest(request);
}
}
// 处理请求
controller.processRequests();
scanner.close();
}
private static Request parseRequest(String input) {
// 内部请求格式: <楼层数>
// 外部请求格式: <楼层数,方向>
if (!input.startsWith("<") || !input.endsWith(">")) {
return null;
}
String content = input.substring(1, input.length() - 1);
String[] parts = content.split(",");
if (parts.length == 1) {
// 内部请求
try {
int floor = Integer.parseInt(parts[0].trim());
return new InternalRequest(floor);
} catch (NumberFormatException e) {
return null;
}
} else if (parts.length == 2) {
// 外部请求
try {
int floor = Integer.parseInt(parts[0].trim());
Direction direction = Direction.valueOf(parts[1].trim().toUpperCase());
return new ExternalRequest(floor, direction);
} catch (NumberFormatException | IllegalArgumentException e) {
return null;
}
}
return null;
}
}
复杂度分析
1、时间复杂度
- RequestQueue类:
- addRequest():O(1)平均,使用HashSet检查重复
- getNextRequest():O(n),需要遍历队列寻找合适请求
- hasRequestAtFloor():O(n),需要遍历队列
- Controller类:processRequests()方法为O(m*n),m为请求数量,n为楼层数
2、空间复杂度
- 程序使用多个队列存储请求,空间复杂度为O(n),n为请求数量
测试方法
1、正常情况测试:
- 内部请求序列:<5> <3> <8>
- 外部请求序列:<2,UP> <4,DOWN> <6,UP>
- 混合请求序列:<5> ❤️,DOWN> <7,UP> <2>
2、边界情况测试:
无效楼层请求:<0> <11>(假设楼层1-10)
重复请求过滤:<3> <3> <3> <4> <4>
边界楼层请求:<1> <10>(最小和最大楼层)
3、特殊情况测试:
- 电梯运行过程中添加新请求
- 多个同方向连续请求
- 反向请求处理
Bug分析
1、潜在问题及解决方案
- 请求解析错误:输入格式不正确时可能解析失败
- 解决方案:在parseRequest()方法中添加异常处理
- 方向判断逻辑:电梯方向判断可能不准确
- 解决方案:优化determineNextDirection()方法逻辑
- 重复请求过滤:HashSet的equals方法需要正确实现
- 解决方案:在Request类中正确重写equals和hashCode方法
2、已解决的问题
- 单一职责原则:各类职责明确分离
- 无效请求处理:自动过滤无效楼层和重复请求
- 调度算法:实现优先同方向的调度策略
面向对象设计与构造——第三次题目集练习总结
7-1:销售步枪问题
题目要求
设计一个步枪销售佣金计算系统,满足以下要求:
1、销售商销售三种步枪部件:枪机(45美元)、枪托(30美元)、枪管(25美元)
2、每月至少售出一支完整步枪,生产限额:枪机70个、枪托80个、枪管90个
3、佣金计算规则:
- 销售额 ≤ 1000美元:10%佣金
- 1000美元 < 销售额 ≤ 1800美元:1000以下部分10%,1000-1800部分15%
- 销售额 > 1800美元:1000以下部分10%,1000-1800部分15%,1800以上部分20%
4、生成月份销售报告,汇总销售总额和佣金
5、必须符合面向对象编程原则,保证类设计的单一职责模式
![image]()
类设计分析
根据UML类图,我们设计以下类结构:
- 销售项类(SalesItem)
职责:封装单个销售项的基本信息
属性:
- name:部件名称
- price:单价
- quantity:销售数量
- maxQuantity:最大销售数量
方法: - 构造方法
- getter方法
- getSubtotal():计算该项小计
- isValid():验证销售数量是否合法
- 销售订单类(SalesOrder)
职责:管理整个销售订单,包含多个销售项
属性:
- items:销售项列表
- salesDate:销售日期
方法: - addItem():添加销售项
- calculateTotalSales():计算总销售额
- validateOrder():验证订单合法性
- getItems():获取销售项列表
- 佣金计算器类(CommissionCalculator)
职责:专门负责佣金计算逻辑
属性:
- commissionRates:佣金率分段配置
方法: - calculateCommission():根据销售额计算佣金
- 可以支持不同的佣金计算策略
- 销售报告类(SalesReport)
职责:生成和格式化销售报告
属性:
- salesOrder:销售订单
- commission:佣金金额
方法: - generateReport():生成销售报告
- displayReport():显示报告
代码实现
点击查看代码
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
// 销售项类
class SalesItem {
private String name;
private double price;
private int quantity;
private int maxQuantity;
public SalesItem(String name, double price, int quantity, int maxQuantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
this.maxQuantity = maxQuantity;
}
public boolean isValid() {
return quantity >= 0 && quantity <= maxQuantity;
}
public double getSubtotal() {
return price * quantity;
}
// Getter方法
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public int getMaxQuantity() { return maxQuantity; }
}
// 佣金计算器类
class CommissionCalculator {
// 佣金率配置:分段阈值和对应费率
private static final double[][] COMMISSION_RATES = {
{1000, 0.10}, // 0-1000: 10%
{1800, 0.15}, // 1000-1800: 15%
{Double.MAX_VALUE, 0.20} // 1800以上: 20%
};
public double calculateCommission(double totalSales) {
if (totalSales <= 0) return 0;
double commission = 0;
double remainingAmount = totalSales;
double previousThreshold = 0;
for (int i = 0; i < COMMISSION_RATES.length; i++) {
double threshold = COMMISSION_RATES[i][0];
double rate = COMMISSION_RATES[i][1];
if (remainingAmount <= 0) break;
double segmentAmount = Math.min(remainingAmount, threshold - previousThreshold);
if (segmentAmount > 0) {
commission += segmentAmount * rate;
remainingAmount -= segmentAmount;
}
previousThreshold = threshold;
}
return commission;
}
}
// 销售订单类
class SalesOrder {
private List<SalesItem> items;
private LocalDate salesDate;
public SalesOrder() {
this.items = new ArrayList<>();
this.salesDate = LocalDate.now();
}
public void addItem(SalesItem item) {
if (item != null && item.isValid()) {
items.add(item);
}
}
public boolean validateOrder() {
// 检查是否至少售出一支完整步枪
boolean hasRifle = false;
int locks = 0, stocks = 0, barrels = 0;
for (SalesItem item : items) {
switch (item.getName()) {
case "Lock": locks = item.getQuantity(); break;
case "Stock": stocks = item.getQuantity(); break;
case "Barrel": barrels = item.getQuantity(); break;
}
}
// 至少售出一支完整步枪(每个部件至少一个)
if (locks > 0 && stocks > 0 && barrels > 0) {
int completeRifles = Math.min(Math.min(locks, stocks), barrels);
hasRifle = completeRifles >= 1;
}
return hasRifle;
}
public double calculateTotalSales() {
double total = 0;
for (SalesItem item : items) {
total += item.getSubtotal();
}
return total;
}
public List<SalesItem> getItems() {
return new ArrayList<>(items); // 返回副本以保护封装性
}
public LocalDate getSalesDate() {
return salesDate;
}
}
// 销售报告类
class SalesReport {
private SalesOrder salesOrder;
private double commission;
public SalesReport(SalesOrder salesOrder, double commission) {
this.salesOrder = salesOrder;
this.commission = commission;
}
public void generateReport() {
displayReport();
}
public void displayReport() {
System.out.println("=== Monthly Sales Report ===");
System.out.printf("Report Date: %s%n", salesOrder.getSalesDate());
System.out.println("----------------------------");
// 显示销售明细
System.out.println("Sales Items:");
for (SalesItem item : salesOrder.getItems()) {
System.out.printf(" %s: %d units @ $%.2f each = $%.2f%n",
item.getName(), item.getQuantity(), item.getPrice(), item.getSubtotal());
}
System.out.println("----------------------------");
double totalSales = salesOrder.calculateTotalSales();
System.out.printf("Total Sales: $%.2f%n", totalSales);
System.out.printf("Commission: $%.2f%n", commission);
System.out.printf("Commission Rate: %.1f%%%n", (commission / totalSales) * 100);
System.out.println("============================");
}
}
// 主程序
public class RifleSalesSystem {
public static void main(String[] args) {
// 创建销售订单
SalesOrder order = new SalesOrder();
// 添加销售项(根据实际销售数据)
order.addItem(new SalesItem("Lock", 45.0, 70, 70)); // 枪机
order.addItem(new SalesItem("Stock", 30.0, 80, 80)); // 枪托
order.addItem(new SalesItem("Barrel", 25.0, 90, 90)); // 枪管
// 验证订单
if (!order.validateOrder()) {
System.out.println("Error: Must sell at least one complete rifle!");
return;
}
// 计算总销售额
double totalSales = order.calculateTotalSales();
// 计算佣金
CommissionCalculator calculator = new CommissionCalculator();
double commission = calculator.calculateCommission(totalSales);
// 生成报告
SalesReport report = new SalesReport(order, commission);
report.generateReport();
}
}
复杂度分析
1、时间复杂度
- SalesItem类:所有方法都是O(1)时间复杂度
- CommissionCalculator类:calculateCommission()方法为O(1),因为佣金分段是固定的
- SalesOrder类:
- validateOrder():O(n),n为销售项数量
- calculateTotalSales():O(n)
- SalesReport类:displayReport():O(n)
2、空间复杂度
- 程序使用O(n)空间存储销售项,n为销售项数量
测试方法
1、正常情况测试:
(1)基础销售:售出1支完整步枪(1枪机+1枪托+1枪管)
- 总销售额:45+30+25=100美元
- 佣金:100×10%=10美元
(2)中等销售:售出10个枪机、15个枪托、20个枪管 - 总销售额:10×45+15×30+20×25=450+450+500=1400美元
- 佣金:1000×10%+400×15%=100+60=160美元
(3)大量销售:售出最大限额(70枪机+80枪托+90枪管) - 总销售额:70×45+80×30+90×25=3150+2400+2250=7800美元
- 佣金:1000×10%+800×15%+6000×20%=100+120+1200=1420美元
2、边界情况测试:
(1)佣金分段边界:
- 销售额=1000美元:佣金=1000×10%=100美元
- 销售额=1001美元:佣金=1000×10%+1×15%=100+0.15=100.15美元
- 销售额=1800美元:佣金=1000×10%+800×15%=100+120=220美元
- 销售额=1801美元:佣金=1000×10%+800×15%+1×20%=100+120+0.2=220.2美元
(2)数量边界: - 最小销售:每个部件1个(必须满足)
- 最大销售:枪机70个、枪托80个、枪管90个
3、异常情况测试:
(1)不完整步枪:只售出枪机和枪托,没有枪管
- 期望:订单验证失败,提示错误信息
(2)超额销售:尝试销售71个枪机 - 期望:SalesItem验证失败,不添加到订单中
Bug分析
潜在问题及解决方案
浮点数精度问题:金额计算可能产生精度误差
- 解决方案:使用BigDecimal进行精确计算,或使用格式化输出控制显示精度
订单验证逻辑:当前验证只检查是否至少有一支完整步枪,可能需要更严格的验证 - 解决方案:可以添加更详细的验证规则,如检查部件数量比例是否合理
佣金计算边界:在佣金分段点可能出现计算错误 - 解决方案:已使用分段累计算法,确保边界值正确计算
已解决的问题
- 单一职责原则:每个类都有明确的单一职责
- 数据验证:在添加销售项时进行数量验证
- 订单完整性:验证是否售出完整步枪
- 佣金计算:正确实现分段佣金计算
7-2:蒙特卡罗方法求圆周率
题目要求
使用蒙特卡洛仿真方法求圆周率π。程序需要从键盘输入五个参数:矩形左上角的横坐标、纵坐标、矩形长度、矩形宽度和投点次数。根据输入参数,在指定矩形区域内进行随机投点,通过统计落在内切圆内的点的比例来估算圆周率π的值。
输入格式:四个实型数和一个整型数,分别为矩形左上角的横坐标、纵坐标、矩形长度、矩形宽度和投点次数,数与数之间可以用一个或多个空格或回车分隔。
输出格式:
- 如果矩形长度与宽度不相等(非正方形)或长宽数据非法,则输出"Wrong Format"
- 如果估算出的π与Math.PI差值小于1E-4,则输出"Success",否则输出"failed"
https://images.ptausercontent.com/5ae5da8b-61b6-4073-86ad-d5be8b64e597.pdf
算法设计与实现
为了更好的代码结构和可维护性,我们可以采用面向对象的设计方法:
点击查看代码
import java.util.Random;
// 点类
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
}
// 矩形类
class Rectangle {
private double x0, y0; // 左上角坐标
private double length, width;
public Rectangle(double x0, double y0, double length, double width) {
this.x0 = x0;
this.y0 = y0;
this.length = length;
this.width = width;
}
public boolean isValidSquare() {
return length > 0 && width > 0 && Math.abs(length - width) < 1e-10;
}
public Point getRandomPoint(Random random) {
double x = x0 + random.nextDouble() * length;
double y = y0 - random.nextDouble() * width; // y轴向下
return new Point(x, y);
}
public Point getCenter() {
return new Point(x0 + length/2, y0 - width/2);
}
public double getRadius() {
return length / 2;
}
}
// 蒙特卡洛仿真器
class MonteCarloSimulator {
private Rectangle rectangle;
private Random random;
public MonteCarloSimulator(Rectangle rectangle) {
this.rectangle = rectangle;
this.random = new Random();
}
public double simulate(int numPoints) {
Point center = rectangle.getCenter();
double radius = rectangle.getRadius();
int pointsInside = 0;
for (int i = 0; i < numPoints; i++) {
Point p = rectangle.getRandomPoint(random);
double dx = p.getX() - center.getX();
double dy = p.getY() - center.getY();
double distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= radius * radius) {
pointsInside++;
}
}
return 4.0 * pointsInside / numPoints;
}
}
复杂度分析
1、时间复杂度
蒙特卡洛仿真:O(n),其中n为投点次数,需要遍历每个点进行判断
2、空间复杂度:O(1),只使用了固定数量的变量,不随投点次数增加而增加
3、算法精度分析
蒙特卡洛方法的精度与投点次数的平方根成反比,即:
- 误差 ≈ 常数 / √n
- 要达到1e-4的精度,需要投点次数约为10^8量级
测试方法与结果分析
1、测试用例设计
正常情况测试:
- 标准单位正方形:0 0 1 1 10000000
- 非原点正方形:1 1 2 2 5000000
- 大面积正方形:0 0 10 10 1000000
边界情况测试:
- 最小合法正方形:0 0 0.001 0.001 1000
- 大量投点:0 0 1 1 100000000
异常情况测试:
- 非正方形:0 0 1 2 1000→ 输出"Wrong Format"
- 负长度:0 0 -1 1 1000→ 输出"Wrong Format"
- 零宽度:0 0 1 0 1000→ 输出"Wrong Format"
2、测试结果验证
使用JUnit进行自动化测试:
点击查看代码
import org.junit.Test;
import static org.junit.Assert.*;
public class MonteCarloPiTest {
@Test
public void testValidSquare() {
Rectangle rect = new Rectangle(0, 0, 1, 1);
assertTrue(rect.isValidSquare());
}
@Test
public void testInvalidSquare() {
Rectangle rect = new Rectangle(0, 0, 1, 2);
assertFalse(rect.isValidSquare());
}
@Test
public void testSimulationAccuracy() {
Rectangle rect = new Rectangle(0, 0, 1, 1);
MonteCarloSimulator simulator = new MonteCarloSimulator(rect);
double estimatedPi = simulator.simulate(1000000);
assertTrue(Math.abs(estimatedPi - Math.PI) < 0.01); // 允许较大误差
}
}
Bug分析与解决方案
1、常见错误及处理
(1)坐标系理解错误:
问题:矩形左上角坐标与y轴方向混淆
解决:明确y轴向下为正的坐标系,正确计算点坐标
(2)浮点数精度问题:
问题:直接使用==比较浮点数判断正方形
解决:使用误差范围比较Math.abs(a - b) < 1e-10
(3)随机数生成效率:
问题:使用Math.random()在大量投点时效率较低
解决:使用Random类提高性能
(4)边界点处理:
问题:圆边界上的点统计不准确
解决:使用<=判断,确保边界点被正确统计
性能优化策略
- 减少函数调用:在循环内避免不必要的函数调用
- 使用更快的随机数生成器:如ThreadLocalRandom
- 并行计算:将投点任务分配到多个线程执行
7-3:单部电梯调度程序(类设计-迭代)
题目要求
基于之前的电梯调度程序进行第三次迭代设计,主要变更包括:
- 加入乘客类(Passenger),取消之前的乘客请求类
- 外部请求格式变更:从<请求楼层数,请求方向>改为<请求源楼层,请求目的楼层>
- 请求处理逻辑变更:处理外部请求后,将目的楼层加入内部请求队列
- 类设计必须遵循单一职责原则(SRP),包含电梯类、乘客类、队列类和控制类
![image]()
类设计分析
根据提供的UML类图,我们重新设计以下类结构:
- 枚举类设计
Direction枚举:
- UP:上行方向
- DOWN:下行方向
- IDLE:空闲状态
State枚举:
- MOVING:移动状态
- STOPPED:停止状态
- 乘客类(Passenger)
职责:封装乘客的乘梯信息
属性:
- sourceFloor:源楼层(外部请求)
- destinationFloor:目的楼层
方法: - 构造方法(支持不同参数)
- getter/setter方法
- getDirection():根据源楼层和目的楼层计算方向
- 电梯类(Elevator)
职责:管理电梯的物理状态和基本操作
属性:
- currentFloor:当前楼层
- direction:当前方向
- state:当前状态
- minFloor、maxFloor:楼层范围
方法: - 移动、开关门等基本操作
- 楼层有效性验证
- 请求队列类(RequestQueue)
职责:管理乘客请求队列
属性:
- 外部请求队列
- 内部请求队列
方法: - 添加、移除请求
- 查询请求状态
- 控制类(Controller)
职责:负责电梯的调度逻辑
属性:
- elevator:电梯对象
- queue:请求队列对象
方法: - processRequests():处理所有请求
- determineDirection():确定方向
- shouldStop():判断是否需要停靠
- removeRequests():移除已处理的请求
代码实现
点击查看代码
import java.util.*;
// 方向枚举
enum Direction {
UP, DOWN, IDLE
}
// 电梯状态枚举
enum State {
MOVING, STOPPED
}
// 乘客类
class Passenger {
private Integer sourceFloor; // 源楼层(外部请求)
private Integer destinationFloor; // 目的楼层
// 构造方法1:外部请求
public Passenger(Integer sourceFloor, Integer destinationFloor) {
this.sourceFloor = sourceFloor;
this.destinationFloor = destinationFloor;
}
// 构造方法2:内部请求
public Passenger(Integer destinationFloor) {
this.sourceFloor = null; // 内部请求没有源楼层
this.destinationFloor = destinationFloor;
}
// 无参构造
public Passenger() {
this.sourceFloor = null;
this.destinationFloor = null;
}
// 获取乘客方向(仅外部请求有效)
public Direction getDirection() {
if (sourceFloor == null || destinationFloor == null) {
return null;
}
return sourceFloor < destinationFloor ? Direction.UP : Direction.DOWN;
}
// Getter和Setter方法
public Integer getSourceFloor() { return sourceFloor; }
public void setSourceFloor(Integer sourceFloor) { this.sourceFloor = sourceFloor; }
public Integer getDestinationFloor() { return destinationFloor; }
public void setDestinationFloor(Integer destinationFloor) {
this.destinationFloor = destinationFloor;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Passenger passenger = (Passenger) obj;
return Objects.equals(sourceFloor, passenger.sourceFloor) &&
Objects.equals(destinationFloor, passenger.destinationFloor);
}
@Override
public int hashCode() {
return Objects.hash(sourceFloor, destinationFloor);
}
}
// 电梯类
class Elevator {
private int currentFloor;
private Direction direction;
private State state;
private int minFloor;
private int maxFloor;
public Elevator(int minFloor, int maxFloor) {
this.minFloor = minFloor;
this.maxFloor = maxFloor;
this.currentFloor = minFloor; // 默认停在最小楼层
this.direction = Direction.IDLE;
this.state = State.STOPPED;
}
// 获取电梯实例(单例模式,可选)
public static Elevator getElevatorInstance(int minFloor, int maxFloor) {
return new Elevator(minFloor, maxFloor);
}
// 验证楼层是否有效
public boolean isValidFloor(int floor) {
return floor >= minFloor && floor <= maxFloor;
}
// 移动一层
public void move() {
if (direction == Direction.UP && currentFloor < maxFloor) {
currentFloor++;
} else if (direction == Direction.DOWN && currentFloor > minFloor) {
currentFloor--;
}
}
// Getter和Setter方法
public int getCurrentFloor() { return currentFloor; }
public void setCurrentFloor(int currentFloor) { this.currentFloor = currentFloor; }
public Direction getDirection() { return direction; }
public void setDirection(Direction direction) { this.direction = direction; }
public State getState() { return state; }
public void setState(State state) { this.state = state; }
public int getMaxFloor() { return maxFloor; }
public int getMinFloor() { return minFloor; }
}
// 请求队列类
class RequestQueue {
private Queue<Passenger> externalRequests; // 外部请求队列
private Queue<Passenger> internalRequests; // 内部请求队列
private Set<Passenger> allRequests; // 所有请求(用于去重)
public RequestQueue() {
this.externalRequests = new LinkedList<>();
this.internalRequests = new LinkedList<>();
this.allRequests = new HashSet<>();
}
// 添加外部请求
public boolean addExternalRequest(Passenger passenger) {
if (passenger == null || passenger.getSourceFloor() == null ||
passenger.getDestinationFloor() == null) {
return false;
}
// 去重检查
if (allRequests.contains(passenger)) {
return false;
}
allRequests.add(passenger);
externalRequests.add(passenger);
return true;
}
// 添加内部请求
public boolean addInternalRequest(Passenger passenger) {
if (passenger == null || passenger.getDestinationFloor() == null) {
return false;
}
// 去重检查
if (allRequests.contains(passenger)) {
return false;
}
allRequests.add(passenger);
internalRequests.add(passenger);
return true;
}
// 获取下一个要处理的请求(调度算法核心)
public Passenger getNextRequest(int currentFloor, Direction currentDirection) {
// 优先处理同方向的请求
if (currentDirection == Direction.UP) {
// 检查内部请求:目的楼层在当前楼层之上
for (Passenger p : internalRequests) {
if (p.getDestinationFloor() >= currentFloor) {
return p;
}
}
// 检查外部请求:源楼层在当前楼层之上且方向一致
for (Passenger p : externalRequests) {
if (p.getSourceFloor() >= currentFloor && p.getDirection() == Direction.UP) {
return p;
}
}
} else if (currentDirection == Direction.DOWN) {
// 检查内部请求:目的楼层在当前楼层之下
for (Passenger p : internalRequests) {
if (p.getDestinationFloor() <= currentFloor) {
return p;
}
}
// 检查外部请求:源楼层在当前楼层之下且方向一致
for (Passenger p : externalRequests) {
if (p.getSourceFloor() <= currentFloor && p.getDirection() == Direction.DOWN) {
return p;
}
}
}
// 如果没有同方向请求,返回任意请求
if (!internalRequests.isEmpty()) {
return internalRequests.peek();
}
if (!externalRequests.isEmpty()) {
return externalRequests.peek();
}
return null;
}
// 检查某楼层是否有需要停靠的请求
public boolean hasStopRequest(int floor, Direction direction) {
// 检查内部请求:目的楼层匹配
for (Passenger p : internalRequests) {
if (p.getDestinationFloor() == floor) {
return true;
}
}
// 检查外部请求:源楼层匹配且方向一致
for (Passenger p : externalRequests) {
if (p.getSourceFloor() == floor &&
(direction == Direction.IDLE || p.getDirection() == direction)) {
return true;
}
}
return false;
}
// 移除已处理的请求
public void removeRequests(int floor, Direction direction) {
// 移除内部请求:目的楼层匹配
internalRequests.removeIf(p -> p.getDestinationFloor() == floor);
// 移除外部请求:源楼层匹配且方向一致
externalRequests.removeIf(p -> p.getSourceFloor() == floor &&
(direction == Direction.IDLE || p.getDirection() == direction));
// 从所有请求中移除
allRequests.removeIf(p ->
(p.getDestinationFloor() != null && p.getDestinationFloor() == floor) ||
(p.getSourceFloor() != null && p.getSourceFloor() == floor &&
(direction == Direction.IDLE || p.getDirection() == direction)));
}
// 处理外部请求:将目的楼层加入内部请求
public void processExternalRequest(Passenger passenger) {
if (passenger == null || passenger.getSourceFloor() == null) {
return;
}
// 移除外部请求
externalRequests.remove(passenger);
allRequests.remove(passenger);
// 添加内部请求(只有目的楼层)
Passenger internalPassenger = new Passenger(passenger.getDestinationFloor());
addInternalRequest(internalPassenger);
}
public boolean hasRequests() {
return !internalRequests.isEmpty() || !externalRequests.isEmpty();
}
}
// 控制类
class Controller {
private Elevator elevator;
private RequestQueue queue;
public Controller() {
// 默认构造
}
public Controller(Elevator elevator, RequestQueue requestQueue) {
this.elevator = elevator;
this.queue = requestQueue;
}
public void processRequests() {
while (queue.hasRequests()) {
// 确定方向
determineDirection();
// 如果没有请求,停止
if (elevator.getDirection() == Direction.IDLE) {
break;
}
// 移动电梯
move();
// 检查是否需要停靠
int currentFloor = elevator.getCurrentFloor();
Direction currentDirection = elevator.getDirection();
if (shouldStop(currentFloor)) {
// 开门
openDoors();
// 处理当前楼层的请求
removeRequests(currentFloor);
// 关门
System.out.println("Close Door");
}
}
}
// 确定电梯运行方向
public void determineDirection() {
Passenger nextRequest = queue.getNextRequest(
elevator.getCurrentFloor(), elevator.getDirection());
if (nextRequest == null) {
elevator.setDirection(Direction.IDLE);
elevator.setState(State.STOPPED);
return;
}
int targetFloor = getTargetFloor(nextRequest);
if (targetFloor > elevator.getCurrentFloor()) {
elevator.setDirection(Direction.UP);
} else if (targetFloor < elevator.getCurrentFloor()) {
elevator.setDirection(Direction.DOWN);
} else {
elevator.setDirection(Direction.IDLE);
}
if (elevator.getDirection() != Direction.IDLE) {
elevator.setState(State.MOVING);
}
}
// 移动电梯
public void move() {
elevator.move();
System.out.println("Current Floor: " + elevator.getCurrentFloor() +
" Direction: " + elevator.getDirection());
}
// 判断是否需要停靠
public boolean shouldStop(int floor) {
return queue.hasStopRequest(floor, elevator.getDirection());
}
// 获取请求的目标楼层
private int getTargetFloor(Passenger passenger) {
if (passenger.getSourceFloor() != null) {
return passenger.getSourceFloor(); // 外部请求:先到源楼层
} else {
return passenger.getDestinationFloor(); // 内部请求:直接到目的楼层
}
}
// 获取下一个要停靠的楼层
public int getNextFloor() {
Passenger nextRequest = queue.getNextRequest(
elevator.getCurrentFloor(), elevator.getDirection());
if (nextRequest == null) {
return elevator.getCurrentFloor();
}
return getTargetFloor(nextRequest);
}
// 找出距离当前楼层最近的楼层
public Integer getClosest(Integer a, Integer b) {
if (a == null) return b;
if (b == null) return a;
int distA = Math.abs(a - elevator.getCurrentFloor());
int distB = Math.abs(b - elevator.getCurrentFloor());
return distA <= distB ? a : b;
}
// 开门操作
public void openDoors() {
System.out.println("Open Door # Floor " + elevator.getCurrentFloor());
}
// 移除已处理的请求
public void removeRequests(int currentFloor) {
// 处理内部请求
queue.removeRequests(currentFloor, elevator.getDirection());
// 处理外部请求:将目的楼层加入内部请求
for (Passenger p : new ArrayList<>(queue.externalRequests)) {
if (p.getSourceFloor() == currentFloor) {
queue.processExternalRequest(p);
}
}
}
// Getter和Setter方法
public Elevator getElevator() { return elevator; }
public void setElevator(Elevator elevator) { this.elevator = elevator; }
public RequestQueue getQueue() { return queue; }
public void setQueue(RequestQueue queue) { this.queue = queue; }
}
// 主程序
public class ElevatorSystem {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取最小和最大楼层
int minFloor = scanner.nextInt();
int maxFloor = scanner.nextInt();
scanner.nextLine(); // 消耗换行符
// 创建电梯系统组件
Elevator elevator = new Elevator(minFloor, maxFloor);
RequestQueue queue = new RequestQueue();
Controller controller = new Controller(elevator, queue);
// 读取乘客请求
while (true) {
String line = scanner.nextLine().trim();
if (line.equalsIgnoreCase("end")) {
break;
}
Passenger passenger = parsePassenger(line, minFloor, maxFloor);
if (passenger != null) {
if (passenger.getSourceFloor() != null) {
queue.addExternalRequest(passenger); // 外部请求
} else {
queue.addInternalRequest(passenger); // 内部请求
}
}
}
// 处理请求
controller.processRequests();
scanner.close();
}
// 解析乘客请求
private static Passenger parsePassenger(String input, int minFloor, int maxFloor) {
if (!input.startsWith("<") || !input.endsWith(">")) {
return null;
}
String content = input.substring(1, input.length() - 1);
String[] parts = content.split(",");
try {
if (parts.length == 1) {
// 内部请求:<楼层数>
int floor = Integer.parseInt(parts[0].trim());
if (floor >= minFloor && floor <= maxFloor) {
return new Passenger(floor);
}
} else if (parts.length == 2) {
// 外部请求:<源楼层,目的楼层>
int sourceFloor = Integer.parseInt(parts[0].trim());
int destFloor = Integer.parseInt(parts[1].trim());
if (sourceFloor >= minFloor && sourceFloor <= maxFloor &&
destFloor >= minFloor && destFloor <= maxFloor &&
sourceFloor != destFloor) {
return new Passenger(sourceFloor, destFloor);
}
}
} catch (NumberFormatException e) {
return null;
}
return null;
}
}
复杂度分析
1、时间复杂度
- RequestQueue类:
- getNextRequest():O(n),需要遍历队列寻找合适请求
- hasStopRequest():O(n),需要遍历队列检查
- removeRequests():O(n),需要遍历队列移除元素
- Controller类:processRequests()方法为O(m×n),m为请求数量,n为楼层数
2、空间复杂度
- 程序使用队列存储请求,空间复杂度为O(n),n为请求数量
测试方法
1、正常情况测试:
(1)简单外部请求:<5,4>(从5楼到4楼)
- 验证:电梯先到5楼接人,再到4楼下人
(2)混合请求:<5,9> <8> <9,3> - 验证:正确处理外部请求转内部请求的逻辑
2、边界情况测试:
(1)连续相同请求:<3><3><3>
- 验证:自动过滤重复请求
(2)边界楼层请求:最小楼层和最大楼层的请求
复杂场景测试: - 多方向请求:同时存在上行和下行请求
- 请求优先级:验证同方向请求优先处理
Bug分析
1、潜在问题及解决方案
(1)方向判断逻辑:外部请求的方向判断需要准确
解决方案:在Passenger类中正确实现getDirection()方法
(2)请求转换逻辑:外部请求转内部请求时需要正确处理
解决方案:在removeRequests()方法中确保正确转换
(3)重复请求处理:使用Set进行去重,确保equals和hashCode正确实现
2、已解决的问题
- 单一职责原则:各类职责明确分离
- 请求格式变更:支持新的<源楼层,目的楼层>格式
- 请求转换机制:正确处理外部请求到内部请求的转换
三次习题集的总结
在面向对象设计原则的实践演进方面:
- 单一职责原则(SRP)
- 初期:一个类承担多个职责(如电梯类既管理状态又处理调度)
- 后期:每个类有明确单一职责,如Passenger、RequestQueue、Controller各司其职
-
- 开闭原则
- 多项式求导中从固定因子到支持扩展的表达式因子
- 佣金计算中可配置的分段费率设计
- 里氏替换原则
- 请求类的继承体系(Request → InternalRequest/ExternalRequest)
- 接口隔离原则
- 后期作业中通过接口明确模块间契约
- 依赖倒置原则
- 控制类依赖于抽象队列接口而非具体实现
在编程基础能力方面:
- Java语法掌握:从基本语法到高级特性熟练应用
- 数据结构选择:根据问题特点选择最优数据结构
- 算法设计:从简单实现到复杂度优化的算法设计
在软件工程能力方面:
1、代码复杂度控制:
初期:方法复杂度高,条件判断嵌套深
后期:方法职责单一,复杂度显著降低
2、测试方法完善:
从手动测试到自动化测试框架
边界测试、异常测试的全面覆盖
3、调试与Bug分析:
从表面现象分析到根本原因定位
预防性编程意识的建立
通过本单元的系列练习,完成了从面向过程思维到面向对象思维的转变,建立了扎实的java设计基础。不仅掌握了技术技能,更重要的是培养了软件工程的系统化思维方式。这种"迭代式学习+实践总结"的模式为后续更复杂的软件系统开发奠定了坚实基础。



浙公网安备 33010602011771号