面向对象程序设计-第一阶段汇报总结-Elvator
面向对象程序设计-第一阶段汇报总结-Elvator
前言
本次阶段持续时间为三周,每个星期完成一个单部电梯小阶段的设计任务。三次作业的难度由浅入深,每一次的侧重点也不同,题目集五的题型较为简单,最后一题设计单部电梯重点为对其进行需求分析和算法设计;题目集六中题量较少,最后一题在前一次单部电梯设计的基础上完成类的划分,使其尽量遵行单一职责原则;题目集七中题量与前一次保持一致,难度相当,最后一题在前一次单部电梯设计的基础上实现迭代。通过第一阶段的学习,我感受到了电梯的无限“魅力”,第一次光是搞懂算法就头晕目眩了,一次又一次的“答案错误”和“运行超时”,调代码调到厌倦,不过把算法搞清楚弄懂之后,后面的第二三次迭代反而轻松不少,通过不断的调试与优化,我深刻感受到了自己设计时的不足之处,但也能发现自己在不断地进步,这也让我认识到一个程序的实现需要一步一步地完成,而不是直接开始写代码。
设计与分析
第一次作业分析
单部电梯调度原题1:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
输入格式
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
- 电梯内乘客请求格式:
<楼层数> - 电梯外乘客请求格式:
<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。 - 当输入“end”时代表输入结束(end不区分大小写)。
输出格式
模拟电梯的运行过程,输出方式如下:
- 运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向> - 运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
题目分析:
题目要求对单部电梯进行分析,与我们现实生活中不一样的是,题目中所要求的电梯只能从内部和外部的第一个开始判定,而后面的请求在前一个没有完成之前并不能实现。一开始分析题目时,并没有弄清楚需求,导致算法设计想了很久,一直与预期不符,后来通过不断地对题目需求及输入样例进行研究,逐渐清楚了应该设计的算法,首先应以内部和外部的请求队列的头一个请求比较,其次是方向优先。除了算法设计,其中还有输入格式的要求,要使得将输入的请求分为内部请求和外部请求,需巧妙地使用正则表达式,将其分开。
类图设计:

由图可知,第一次并未对类的单一设计做要求,而是先把其需求和算法弄清楚,基本实现单部电梯调度的功能。
主要的Elevator类中涉及的方法有:
- 对电梯运行方向的判断。
- 对当前楼层是否停靠的判断。
- 将已完成的请求请出相应的队列。
- 楼层的开关门输出。
- 电梯一层一层移动的总方法。
SourceMonitor分析:

由图可知,Avg Method 的指数较高,表明代码中方法调用较为频繁,需要检查方法调用的情况,减少冗余调用,让代码在实现功能的基础上更加简洁,提高代码的可读性,进而也减少程序占用的资源;Complexity 的指数较高,表明代码的算法逻辑较为复杂,分支判断语句较多,需要进一步梳理逻辑,优化算法,减少不必要的分支判断,提高代码的执行效率。
第二次作业分析
单部电梯调度原题2:
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
输入格式
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
- 电梯内乘客请求格式:
<楼层数> - 电梯外乘客请求格式:
<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。 - 当输入“end”时代表输入结束(end不区分大小写)。
输出格式
模拟电梯的运行过程,输出方式如下:
- 运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向> - 运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
题目分析:
本次题目是在前一次的基础上做类的划分,为了确保类设计遵循单一职责原则(SRP),前一次的功能全部都在elevator一个类里面,这样降低了代码的可读性和可维护性。本次题目要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,其中控制类专门负责电梯调度过程,并且增加了输入请求的判断,要求将与前一次重复输入的请求不存入队列中,这需要我们在使用正则表达式提取请求之后再对是否增加到队列中做出判断。
类图设计:

- ExternalRequest类
- 外部请求队列的无参和含参构造方法。
- 外部请求队列的楼层及方向的getter和setter方法。
- Elevator类
- 电梯的无参和含参构造方法。
- 电梯当前楼层,方向,状态,最大楼层,最小楼层的getter和setter方法。
- 判断楼层是否合法。
- RequestQueue类
- 内外部请求队列的构造方法。
- 内部、外部请求队列的getter和setter方法。
- Controller类
- 控制类构造方法。
- 电梯和请求队列的getter和setter方法。
- 找出下一停靠楼层的方法。
- 判断电梯运行方向的方法。
- 删除对应请求队列的方法。
- 电梯一层一层移动的方法。
- 电梯输出开门关门的方法。
-
Direction类(枚举)
-
State类(枚举)
-
Main类(主方法)
SourceMonitor分析:

由图可知,Avg Method 的指数明显减小,表明代码调用方法的平均语句减小,使得代码更加简洁,占用资源减少;Complexity 的平均复杂程度相比于前次指数减少至绿圈范围内,表明代码的复杂程度降低,执行效率更高;但仍然存在,个别复杂程度较高的方法。
第三次作业分析
单部电梯调度原题3:
电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
题目分析:
本次题目在前一次的基础上迭代,要求包含但不限于设计电梯类、乘客类、队列类以及控制类。并且输入格式发生改变,外部请求由原来的请求楼层+请求方向变为请求楼层+目标楼层,且在执行完外部请求后,要将外部请求中的目标楼层加入内部请求的末尾,为了实现这部分的迭代,需要将输入时正则表达式的匹配形式进行调整,并且要通过外部请求的两个楼层判断其请求方向。
类图设计:

- Passenger类
- 乘客请求的构造方法。
- 请求楼层,目标楼层,请求方向的getter和setter方法。
- Elevator类
- 电梯的无参和含参构造方法。
- 电梯当前楼层,方向,状态,最大楼层,最小楼层的getter和setter方法。
- 判断楼层是否合法。
- RequestQueue类
- 内外部请求队列的构造方法。
- 内部、外部请求队列的getter和setter方法。
- Controller类
- 控制类构造方法。
- 电梯和请求队列的getter和setter方法。
- 找出下一停靠楼层的方法。
- 判断电梯运行方向的方法。
- 删除对应请求队列的方法。
- 电梯一层一层移动的方法。
- 电梯输出开门关门的方法。
-
Direction类(枚举)
-
State类(枚举)
-
Main类(主方法)
SourceMonitor分析:

由图可知,各项指数与前一次代码基本一致,因为只改变了部分地方如将内部请求和外部请求都用Paasenger类来实现。Complexity 指数依然较高,表明代码的一些方法复杂程度较高,分支语句较多,仍需要进一步调整优化,使代码更高效简洁。
踩坑心得
- 在第一次的算法设计时,并未弄清楚电梯是如何运行的,导致一直以现实生活中的电梯例子分析算法,即内部队列和外部队列一起处理,而不是只比较头指针,感觉第一个星期每天都在研究算法,一天一个样,这也导致自己在一开始的算法实现中走了很多弯路。
- 在弄清楚算法逻辑后,便准备开始写代码,由于刚开始对正则表达式的不熟悉,导致前几次的代码一直都只能加入第一个外部请求队列,后面就加不了了,导致一直答案错误。后来我就把外部队列的楼层请求和方向请求分开,结果得到了绿色的“非零返回”...,不过通过一直调试了多种情况后,终于看到了红色的“答案正确”。
![]()
- 在第二次作业之前,老师补发了一些测试样例,于是我拿这些测试样例去测试第一次的代码,得到了完美的“运行超时”,为了之后第二次的分类设计,我就决定先去调试了无数次第一次的代码,在一步一步的调试过程中,我可以很清楚的看到哪一步使程序进入了死循环,于是我就改了又改,也只解决了部分运行超时的问题。
- 在第二次作业中,与前一次的算法逻辑不一样,在之前我想的是每到一层再判断开不开门,这导致我的电梯移动方法极其复杂,且要改其他地方时非常麻烦,于是我换了一种算法逻辑,即增加一个找到下一个要开门的楼层的方法,这样使得我的代码逻辑更清楚,方法不累赘,可维护性也得到了提高。
- 实现第二次算法中一样遇到了运行超时的问题,我不得不再次去不断地调试,每天都在调试的路上,最后发现是自己的请求对联删除的方法存在问题。
以下是修改前的部分代码:
点击查看代码
`public int nextfloor() {
if(request.getExternal().isEmpty()&&request.getInternal().isEmpty())
return elevator.getCurrentfloor();
else if(request.getExternal().isEmpty()&&!request.getInternal().isEmpty())
return request.getInternal().getFirst();
else if(!request.getExternal().isEmpty()&&request.getInternal().isEmpty())
return request.getExternal().getFirst().getFloor();
else
{
if(elevator.getPlayway()==Direction.UP) {
if(request.getInternal().getFirst()>elevator.getCurrentfloor()) {
if(request.getExternal().getFirst().getFloor()>elevator.getCurrentfloor()&&request.getExternal().getFirst().getDirection()==Direction.UP) {
int i=Math.abs(request.getInternal().getFirst()-elevator.getCurrentfloor());
int e=Math.abs(request.getExternal().getFirst().getFloor()-elevator.getCurrentfloor());
if(e<i)
return request.getExternal().getFirst().getFloor();
}
else
return request.getInternal().getFirst();
}
if(request.getInternal().getFirst()<elevator.getCurrentfloor()) {
if(request.getExternal().getFirst().getFloor()>elevator.getCurrentfloor())
return request.getExternal().getFirst().getFloor();
else if(request.getExternal().getFirst().getFloor()<elevator.getCurrentfloor()&&request.getExternal().getFirst().getDirection()==Direction.DOWN) {
int i=Math.abs(request.getInternal().getFirst()-elevator.getCurrentfloor());
int e=Math.abs(request.getExternal().getFirst().getFloor()-elevator.getCurrentfloor());
if(e<i)
return request.getExternal().getFirst().getFloor();
}
else
return request.getInternal().getFirst();
}
}
else if(elevator.getPlayway()==Direction.DOWN){
if(request.getInternal().getFirst()<elevator.getCurrentfloor()) {
if(request.getExternal().getFirst().getFloor()<elevator.getCurrentfloor()&&request.getExternal().getFirst().getDirection()==Direction.UP) {
int i=Math.abs(request.getInternal().getFirst()-elevator.getCurrentfloor());
int e=Math.abs(request.getExternal().getFirst().getFloor()-elevator.getCurrentfloor());
if(e<i)
return request.getExternal().getFirst().getFloor();
}
else
return request.getInternal().getFirst();
}
if(request.getInternal().getFirst()>elevator.getCurrentfloor()) {
if(request.getExternal().getFirst().getFloor()<elevator.getCurrentfloor())
return request.getExternal().getFirst().getFloor();
else if(request.getExternal().getFirst().getFloor()>elevator.getCurrentfloor()&&request.getExternal().getFirst().getDirection()==Direction.DOWN) {
int i=Math.abs(request.getInternal().getFirst()-elevator.getCurrentfloor());
int e=Math.abs(request.getExternal().getFirst().getFloor()-elevator.getCurrentfloor());
if(e<i)
return request.getExternal().getFirst().getFloor();
}
else
return request.getInternal().getFirst();
}
}
}
return elevator.getCurrentfloor();
}
public void removeRequest() {
if(!request.getExternal().isEmpty())
{
if(request.getExternal().getFirst().getFloor()==elevator.getCurrentfloor()&&request.getExternal().getFirst().getDirection()==elevator.getPlayway())
request.getExternal().removeFirst();
}
if(!request.getInternal().isEmpty()) {
if(request.getInternal().getFirst()==elevator.getCurrentfloor())
request.getInternal().removeFirst();
}
}`
更改后的部分代码:
点击查看代码
`public int nextfloor() {
if (request.getExternal().isEmpty() && request.getInternal().isEmpty()) {
x=0;
return elevator.getCurrentfloor();
}
int currentFloor = elevator.getCurrentfloor();
Direction currentDir = elevator.getPlayway();
if (request.getExternal().isEmpty()) {
x=1;
return request.getInternal().getFirst();
}
if (request.getInternal().isEmpty()) {
x=-1;
return request.getExternal().getFirst().getFloor();
}
int internalFloor = request.getInternal().getFirst();
ExternalRequest externalReq = request.getExternal().getFirst();
int externalFloor = externalReq.getFloor();
Direction externalDir = externalReq.getDirection();
if (currentDir == Direction.UP) {
if (internalFloor > currentFloor && externalFloor > currentFloor&&externalDir==Direction.UP) {
if((externalFloor - currentFloor) <= (internalFloor - currentFloor)) {
x=-1;
return externalFloor;
}
else
{
x=1;
return internalFloor;
}
} else if (internalFloor > currentFloor) {
x=1;
return internalFloor;
} else if (externalFloor > currentFloor) {
x=-1;
return externalFloor;
} else
{
if(externalDir==Direction.UP) {
x=1;
return internalFloor;
}
else {
if((currentFloor - externalFloor) <= (currentFloor - internalFloor)){
x=-1;
return externalFloor;
}
else {
x=1;
return internalFloor;
}
}
}
}
else {
if (internalFloor < currentFloor && externalFloor < currentFloor&&externalDir==Direction.DOWN) {
if((currentFloor - externalFloor) <= (currentFloor - internalFloor)){
x=-1;
return externalFloor;
}
else {
x=1;
return internalFloor;
}
} else if (internalFloor < currentFloor) {
x=1;
return internalFloor;
} else if (externalFloor < currentFloor) {
x=-1;
return externalFloor;
} else {
if(externalDir==Direction.DOWN) {
x=1;
return internalFloor;
}
else {
if((externalFloor - currentFloor) <= (internalFloor - currentFloor)) {
x=-1;
return externalFloor;
}
else
{
x=1;
return internalFloor;
}
}
}
}
}
public void removeRequest(int k) {
if(!request.getExternal().isEmpty()&&k==-1) {
elevator.setPlayway(request.getExternal().getFirst().getDirection());
request.getExternal().removeFirst();
if(!request.getInternal().isEmpty())
{
if(request.getInternal().getFirst()==elevator.getCurrentfloor())
request.getInternal().removeFirst();
}
}
if(!request.getInternal().isEmpty()&&k==1) {
request.getInternal().removeFirst();
if(!request.getExternal().isEmpty()) {
if(request.getExternal().getFirst().getFloor()==elevator.getCurrentfloor()&&request.getExternal().getFirst().getDirection()==elevator.getPlayway()) {
request.getExternal().removeFirst();
}
}
}
}`

部分代码如下:
点击查看代码
`private static boolean judgeExternalRequest(LinkedList<ExternalRequest> externalRequests, int floor, Direction direction) {
if(externalRequests.isEmpty()) {
return true;
}
else {
if(externalRequests.getLast().getFloor()==floor&&externalRequests.getLast().getDirection()==direction)
return false;
else
return true;
}
}
private static boolean judgeInternalRequest(LinkedList<Integer> internalrequest,int floor) {
if(internalrequest.isEmpty())
return true;
else {
if(internalrequest.getLast()==floor)
return false;
else
return true;
}
}`
- 在第三次作业中,因为通过前两次的不断调试,程序实现基本完整,第三次只用增加一个乘客类,将请求用乘客类存入,即可完成第三次作业的迭代。果然每个程序都是一步一步完成的,只有前面的步骤搞好了,才会越做也简单,越做越轻松,虽然前面的算法设计与调试真的很痛苦,但好在自己坚持下来了,写出来的那一刻也是满满成就感。
改进建议
- 虽然本阶段实现了较为完整的单部电梯调度问题,但从代码的分析可以看出,算法逻辑过于复杂,不断地分支语句判断降低了代码的可读性,说明算法逻辑还需要进一步优化,在下次阶段的设计时应尽可能简便算法。
- 在编写代码时,应该遵循单一职责原则,将代码模块化,每个模块只负责一个功能,这样能够提高代码的可读性和可维护性,这样在之后的对程序迭代是改起来会较为方便。
- 提升自己的算法思维能力,多思考问题,多理解需求,只有自己的思维能力提高了,算法设计才能更简便,减少过多的复杂逻辑。同时需求分析和算法设计是最需要时间的,我们要让自己冷静下来,不然只会越想越心累,越想越复杂。
- 需求分析需要多花时间,一个问题的来源就是需求,只有把每个问题的需求分析好,弄清楚要解决的问题有哪些,实现的功能有什么,保证需求分析完整,才能更好地完成对程序的设计。
- 提升自己的社交能力和团队协作能力,一个人的力量是狭小的,如果只靠一个人的能力是很难解决一整个问题的,在这次阶段性作业中我意识到了交流的重要性,再互相借鉴的交流中我们能了解到更多不同的思维方式,这样也能避免自己花费大量走弯路。
总结
通过本阶段三次的题目集的训练,加深了我对这门课学习的理解,不仅是编程能力的体现,我们也要注重面向对象的需求,清楚程序所要解决的问题。通过不断地对代码进行调试,也使得我更加熟悉对于IDE工具的使用,让代码不出错且更加高效。总的来说,经历了三周的奋斗,我感受到自己在不断地进步,同时也发现自己的诸多不足之处,之后也将秉持着良好的学习态度不断努力提升自己的能力。


浙公网安备 33010602011771号