南昌航空大学Java题目集OOP5~7大作业分析Blog1

一、前言

目前Java才学习了一个月多一点,从刚开始C语言题目用Java语言来写到Java大作业,对于刚开始的我来说还是有点吃力的。
大作业总共分成三次迭代,这次大作业是电梯的调度问题,属于中等偏上难度。
第一次主要是锻炼我们的逻辑思维能力,同时包含正则表达式,类与对象,列表,封装性等知识点;
第二次在前面的基础上包含类的设计等知识点,提高我们面向对象的能力,同时题目也对请求进行了修改,删除了一些不符合要求的请求;
第三次又在前面的基础上增加面向对象的单一职责原则,提高代码的健壮性,同时题目也修改了外部请求,将原来的 <当前楼层,方向>改成<当前楼层,目标楼层>。
这三次代码效率越来越高,越来越符合面向对象的原则。后续我将对此次大作业进行分析。

二、设计与分析

电梯第一次作业

题目:

7-5 NCHU_单部电梯调度程序
分数 70
中等
作者 段喜龙
单位 南昌航空大学
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。

分析:

源码类图:

源码报表分析图:

图表分析:

(1) 代码规模
总行数:216行,属于中等规模的代码文件。
语句数:169个,相对于行数来说代码密度较高。
(2) 代码复杂性
最大复杂度:65,这是一个极高的值,表明存在一个方法(Elevator.is_in())的逻辑极其复杂,这主要是因为第一次写如此大规模的Java代码,还不了解面向对象的真正要求,仍然使用C语言面向过程的思维,我会慢慢地将我的思维转换过来。
平均复杂度:7.11,相对来说复杂程度较高,需要继续将复杂问题模块化、简单化,从而降低复杂度,提高代码效率,在第三次的代码中将会进行修改,得到一个复杂度相对较低的代码。
最复杂方法:Elevator.is_in(),复杂度高达65,这是一个严重问题,后续将会对这个方法进行修改,从而降低它的复杂度,使整个代码的效率越来越高。
(3) 代码结构
最大代码块深度:7,表明存在非常深的嵌套结构,这也是来自C语言的思维,在未来编写代码的过程中我会注意这一点,改善我的代码。
平均代码块深度:3.47,这个值偏高,说明代码中有较多嵌套层次。
(4) 代码注释
含释行的百分比:0.0%,代码完全没有注释,这是由于我想要尽快地完成PTA,以及命名都是英文命名,便于理解,于是就没有标注释。
(5) 代码块深度分析
最深嵌套达到7层,出现在第147行,这样的深度会使代码难以理解和维护,我会尽量优化我的代码。
平均嵌套深度3.47表明整体代码结构偏复杂。
(6) 分支和方法调用
分支语句百分比:33.7%,这个比例较高,增加了代码的复杂性,这是由于当时还不了解单一职责原则,以后我会不断的进行优化。
方法调用语句数:83个,相对于169个总语句数来说调用频繁(约49%)。

分析总结:

这是我第一次写如此大规模的Java代码,对Java里面的原则及要求理解还不够透彻,并且还保留着C语言的思考方式,这导致我犯了许多面向对象中的错误,虽然我的代码在PTA中获得了满分,但仍存在思维上的错误,需要我在一次次编程中将我的思维转换过来。
这个代码只有Main和Elevator这两个类,将所有的职责全部放到Eelvator这个大类里面,使得复杂度和效率大打折扣,完全不符合面向对象的要求。

电梯第二次作业

题目:

对之前电梯调度程序进行迭代性设计,乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
注意:本次作业类设计必须符合如上要求(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次电梯程序提交到本次题目中测试)。

分析:

源码类图:

源码报表分析图:

图表分析:

(1) 代码规模
总行数:276行,属于中等规模的代码文件。
语句数:193个,代码密度适中。
(2) 代码复杂性
最大复杂度:65,仍然是Controller.is_in()方法,该方法与第一次的方法很相似,没有进行很大修改。
平均复杂度:6.04,表明整体代码复杂度还是偏高。
最复杂方法:Controller.is_in(),复杂度65。
(3) 代码结构
最大嵌套深度:7层(出现在第205行),是由于这次代码没有进行很大修改。
平均嵌套深度:3.38,表明代码中存在较多嵌套结构。
方法分布:每个类平均5.75个方法,每个方法平均6.87条语句,结构尚可但需优化复杂度。
(4) 代码注释
严重缺陷:注释行百分比为0.0%。
(5) 分支和方法调用
分支语句占比:31.6%,属于较高水平,增加了逻辑复杂度。
方法调用数:129次,相对于193个总语句(占比约67%),调用频率过高,可能影响性能。

分析总结:

这次代码的分析结果与第一次及其相似,主要是因为这次题目只是请求进行了修改,删除了一些不符合要求的请求,其它的不变。于是我就只对输入后所得的请求队列进行修改,便将代码提交到PTA里,最终得到了满分,可还没高兴三秒同学告诉我要分类,否则0分,我是万万没想到没有抄袭与被抄袭都会判0分。当时我十分的生气,无论我之后怎么修改都是0分,我就打算干脆不改了,但我想想之后还要用到这次代码,我就将一个类分成了几个类,没有对里面的算法进行修改。所以这次代码的数据分析与第一次代码的数据分析有很多相似之处,在第三次的代码中我进行了全面优化,相对来说会比较完善一点。
该代码将原来的2个类分成4个类,有点符合面向对象的分类要求,但根据数据分析可以看出2/3的代码都在Controller这个类里面,几乎所有的功能都在这个类里面,而且这个类中的is_in()方法的复杂度高达65,极大的超出了正常代码的复杂度,还需要继续继续改进。
根据类图可以看出类与类之间的关系十分复杂,耦合行也很强,如何后续出现bug就很难修复,需要动比较大的手术,不利于代码的维护,所以在之后编写代码时我需要进行改进,在本次代码中吸取教训。

电梯第三次作业

题目:

对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。

电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
注意:本次作业类设计必须符合如上要求(包含但不限于设计电梯类、乘客类、队列类以及控制类),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次及第二次电梯程序提交到本次题目中测试)。

分析:

源码类图:

源码报表分析图:

图表分析:

(1) 代码规模
总行数:272行,属于中等规模的代码文件
语句数:172个,代码密度适中
(2) 代码复杂性
最大复杂度:8(InputHandler.parseInput()方法),处于可接受范围上限,其有如此高的复杂度主要是要判断请求是否合理
平均复杂度:1.37,整体复杂度相对较低,效率能够提高,该代码相对来说比较良好
最复杂方法:InputHandler.parseInput(),复杂度8
(3) 代码结构
最大嵌套深度:9+层(出现在第266行),这比之前的最大嵌套深度有所提高,这是我当时修改代码时没有注意到的
平均嵌套深度:3.82,表明部分代码块嵌套较深
方法分布:每个类平均5.83个方法,每个方法平均3.54条语句,这点相比较与前两个代码控制的更好,对前面代码进行了优化
(4) 代码注释
注释行百分比:7.0%,在完成PTA作用是我没有太注重于写注释,所以注释百分比比较低
(5)分支和方法调用
分支语句占比:分支占比极低(7%),控制流简单
方法调用数:方法调用语句数为 93次,相对于172总语句数,调用占比高达 54%

分析总结:

本次代码对前两次的代码进行了全面优化,复杂度大大的降低,将原来复杂度最高的Controller.is_in()从65降到了8。其中主要操作就是将请求列表的String类型转变成int[]类型,String类型需要遍历字符串查找数字,而int[]直接就给出来数字,可以直接进行比较,改进前后代码分别如下:
改进前:

public boolean is_in(String direction,int cur, String in, String out) {
        String outDir = out.replaceAll("[^a-zA-Z]", "");
        int outNum = Integer.parseInt(out.replaceAll("\\D+", ""));
        int inNum = Integer.parseInt(in.replaceAll("\\D+", ""));

        if (direction.equals("UP")) {
            if (outDir.equals("DOWN")) {
                if(inNum>cur)return true;
                else return inNum>outNum;
            }else{
                if(inNum>cur&&outNum>cur)
                    return inNum<outNum;
                else if(inNum>cur&&outNum<cur)
                    return true;
                else if(inNum<cur&&outNum>cur)
                    return false;
                else if(inNum<cur&&outNum<cur)
                    return inNum>outNum;
            }
        } else if (direction.equals("DOWN")) {
            if (outDir.equals("UP")) {
                if(inNum<cur) return true;
                else return inNum<outNum;
            }else {
                if(inNum<cur&&outNum<cur)
                    return inNum>outNum;
                else if(inNum<cur&&outNum>cur)
                    return true;
                else if(inNum>cur&&outNum<cur)
                    return false;
                else if(inNum>cur&&outNum>cur)
                    return inNum<outNum;
            }

        }
        return false;
    }

改进后:

public void addin(String input) {
        int []floor=new int[2];
        floor[0]=Integer.parseInt(input.replaceAll("\\D+",""));
        in.add(floor);
    }
 public void addout(String input) {
        int []floor=new int[2];
        String []attrs=input.substring(input.indexOf("<")+1,input.indexOf(">")).split(",");
        floor[0]=Integer.parseInt(attrs[0]);
        floor[1]=Integer.parseInt(attrs[1]);
        out.add(floor);
    }

这段代码直接将输入的数据转化成int[2],第1个数据是按电梯的楼层,第2个数据是目标楼层,如果是电梯类请求,其第二个数据便无需用到直接变成0,不将它直接变成int类型主要是由于后面便于编写代码,与out是一样的操作。

public boolean is_in(String direction,int cur, int []in, int []out) {
        int outNum = out[0];
        int inNum = in[0];
        String outDir=(out[1]>out[0])?"UP":"DOWN";
        if (direction.equals("UP")) {
            if(outDir.equals("DOWN")){
                return inNum > cur || (inNum < cur && outNum < cur && inNum > outNum);
            }else {
                return (inNum > cur && (outNum < cur || inNum < outNum)) || (inNum < cur && outNum < cur && inNum > outNum);
            }
        } else if (direction.equals("DOWN")) {
            if(outDir.equals("UP")){
                return inNum < cur || (inNum > cur && outNum > cur && inNum < outNum);
            }else {
                return (inNum < cur && (outNum >= cur || inNum > outNum)) || (inNum > cur && outNum >= cur && inNum < outNum);
            }
        }

        return false;
    }

代码中最大复杂度是8,是InputHandler.parseInput(),主要是因为它需要处理一些不合理的请求,所以需要很多判断,但总的来说,整个代码的复杂度都是偏低的,是一个比较完善的代码。

三、踩坑心得

  • 在第一次作业中,由于刚接触电梯题目,对其中的运行原理不是很清楚,每次在IDEA中可以通过题目中唯一的测试样例,但在PTA中却错误。在老师每天分享解题思路情况下,我修改了4次我的运行原理,最终才通过了PTA测试点。这4次是完全不同的运行原理,期间还修改了无数次细微的原理,最后提交了十多次才通过。提交记录如下:

  • 第二次作业由于急于求成,在只看到题目迭代的变化后就修改源代码,直接提交得了满分,却没有看到注意,直接得0分。这也让我知道仔细身体的重要性,从中吸取了教训。
  • 第三次作业第一大问题还是运行原理问题,对于一些电梯运行原理理解错误,如:
1
20
<5,4>
<5>
<7>
end

我一直以为电梯外部请求列和内部请求列第一个只要相同就会只开一次门,认为开门情况是5->7->4,但询问老师后才知道是否只开一次门看要看运行方向,当在1楼时电梯方向UP,而<5,4>方向DOWN,固电梯先完成<5>请求,方向UP,再完成<7>请求,最后才完成<5,4>请求。

第二大问题便是运行超时

这是我三次写电梯题目第一次遇到的情况,这其实是最难修改的,在想了两天后,我觉得对Controller.process()进行修改,将原来的

i < request.getOut().size() && j < request.getIn().size()
i == request.getOut().size()
j == request.getIn().size()

三种情况合分别讨论合并成

i < request.getOut().size() || j < request.getIn().size()

合并后需要对is_in()和cur_dir()进行修改,然后分情况求出下一楼层,在同一求出方向,最后同一进行输出,大大降低运行时间,最终通过全部测试点。

四、改进建议

  • 在以后的代码中还是需要添加一些注释,如果遇到很复杂的问题,经过几天后可能会忘记自己写了什么。
  • 提高单一职责原则能力,多分一些方法,实现职责单一。
  • 减少嵌套,嵌套过深代码的可读性和维护性都会降低,如果要修改一处,很多地方都需要修改,可能会出现遗漏错误,最终导致代码错误。
  • 多加学习语法,在编程的过程中遇到很多不会的知识点都需要到各处查找资料,有时只能用暴力的方法,这样效率又会大大降低。

五、总结

代码总结:

经过这次大作业的几次迭代,我的思维也潜移默化的从C语言中的面向对象逐渐转化到面向过程,不再会把所有功能都放在一个类里或者将一个方法写到七八十行,这样可以增加代码的复用性与安全性。本次大作业我懂得了如何分析题目要求,再进行画类图,合理地设计类并将各个类用接口联系起来,同时代码中用到了很多列表与正则表达式,这让我对它们有很深的了解,尤其是正则表达式,它的效率比C语言中的方法高效的多,能够很好的处理字符串问题,从字符串中提取数据,在本次代码中主要用于请求数据的拆分,提取按电梯的楼层以及方向或目标楼层。除此之外我还学会了使用PowerDesigner与SourceMonitor两大软件,一个用于画类图,一个用于分析代码,对我们以后变成有很大帮助。

建议:

  • 实验源码能不能开放粘贴功能,在提交系统中编写代码效率会大大降低,浪费我们学生很多的时间,并且起不到什么作用,提交系统中不仅没有快捷键,还不能看到自己在打代码时有哪些地方打错了,最终提交发现自己有五十多出错误,还得去一一查看自己是否是拼写错误。如果只是为了防止抄袭而影响我们的效率,我认为大可不必,因为想要抄袭的人自会想尽一切办法。
  • PTA得分代码界定可不可改成最后一次提交的代码,因为有时会没看清一些要求就提交,或者不小心复制了一些不符合要求的代码上去并提交得了满分,导致没有修改的机会,这些情况没有因为抄袭而被判定0分我认为很冤。并且有时会不知道自己的代码是否已经完全符合题目要求,就不敢轻易提交,最终也不知道自己代码的运行原理是否正确,如果改成最后一次提交的代码,我们就敢于提交,查看自己代码原理是否错误,并且可以不断地进行修改,最终提交自己满意的代码。

posted on 2025-04-19 11:05  ~浮~  阅读(69)  评论(0)    收藏  举报

导航