NCHU电梯blog作业
前言
经历了3周面向对象编程的学习后,第一单元的课程就结束了。予我而言,收获颇丰。
1.知识点
主要在电梯运行题目里花费了很大的功夫,从这次的迭代练习里面,我了解并运用了一些重要的面向对象程序的重要概念如:单一职责原则 (Single Responsibility Principle, SRP),开闭原则 (Open/Closed Principle, OCP)等等。还有逐渐形成了面向对象编程的思维。
同样,还学习到了一些硬通货,比如对于正则表达式,ArrayList等等的运用。
Complexity Metrics(复杂度分析)
因为下面要用到复杂度分析,所以先在此给出一些相关概念。
我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。
ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在[1,v(G)]之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在[1,v(G)]之间,值越大联系越紧密。
v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
2.题量
本次练习是3次的迭代练习,每一次在前一次的基础上进行少部分的功能增加同时还有部分代码逻辑以及结构上的更改。
比如第一次就只是完成电梯的功能就行,到了第二次,我们就需要增加输入过滤的功能以及开始将里面的代码按照类图进行创建类。
3.难度
于我而言,对于第一次电梯的基本功能实现就显得非常困难,感觉除了处理完一开始的乘客输入后,我就很难对后面的电梯运行逻辑去编写了。整个逻辑就像一团乱麻,不知道从哪里下手。
后面第二次的作业,要求进行根据类图来书写代码,在那次的作业里,加入了过滤功能,但是那个并没有什么难度,而是在对于不同类的使用,以及他们之间的关系,还有在类里面创建一个的类并且对他进行使用很难。这个很考验对类和对象的理解,如果第一次写出来了,并且有足够强大的理解,把他写出应该难度不大。
最后一次作业,要求加入乘客类,并且对乘客的请求做了些许的改变。如果第二次也写出来了,我觉得第三次作业应该也不难。
下面我将从程序结构,公测、互测以及bug分析几个方面来总结我第一单元的三次作业。
PTA第一次题目集
题目:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。
电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。
每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。
为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
思考:
对于第一次遇见这种大规模的作业,其实我有点手足无措,开始把请求的过滤写完之后,我就开始分析内部电梯逻辑的实现,但是好像对我来说有点困难。
首当其冲的就是能够分析明白电梯的运行规则,这个对我来说就有点困难。后来,我们需要书写电梯类Elevator来进行调度电梯
类图:
由于这次没有要求有太过复杂的类图要求,所以第一次就没有画图了
复杂度分析:
代码规模 :行数(Lines):255 行,说明代码量相对较多。
语句数(Statements):。
结构与逻辑复杂度:
分支语句占比(Percent Branch Statements):33.1% ,表明代码中条件判断(如if - else、switch 等)相关逻辑较多,程序执行路径丰富,逻辑分支复杂。 -
方法调用语句数(Method Call Statements):66 条
注释情况 :
含注释行的比例(Percent Lines with Comments):仅 0.4% ,注释极少,不利于理解代码逻辑和功能,会给代码维护和阅读带来较大困难。
**图表信息 **:
雷达图(Kiviat Graph):从各维度综合看,注释比例低,方法平均语句数较多等情况与上述分析对应,反映代码在注释、复杂度等方面存在不足。
块直方图(Block Histogram):展示了不同深度代码块的语句分布,可看出代码块深度集中在某些区间,说明代码存在一定嵌套层次,深度较大处代码逻辑相对复杂。
BUG分析:
公测:
无问题
互测:
无问题
测试方法:
通过PTA上的测试案例以及老师的补充案例来测试程序的正确性。同时,对程序输出结果进行详细记录和分析,观察电梯运行过程是否符合预期。
PTA第二次题目集
题目:
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
注意:本次作业类设计必须符合如上要求(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次电梯程序提交到本次题目中测试)。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
思考:
对于这次的作业,老师已经给出了很详细的类图设计,而且里面的乘客需求也很简单,只需要用正则表达式把多余的请求过滤掉就好啦。然后按照类图,把原本的代码给拆解好变成几个类就好了
但是,这次作业我没做出来,所以我在此分析我的作业存在的问题、心历路程、感悟
这次作业一开始是抱着很简单的心理去做的,因为看那些要求似乎不难(当然只是看着),然后花费没多少时间就成功把乘客多余请求过滤掉了。后来开始进行类的设计,当然内部成员没什么问题,直到开始设计Controller类的主要方法,里面的几个方法每个要求的功能都不一样,要求实现一个方法只做一件事,这个时候,对于每个成员方法的使用就显的尤为重要,我总是忘记变量来自哪里,还有要不要重新创建,以及陷入创建完新的对象会不会冲突的问题。这导致我的代码非常混乱,后来勉强解决了这个问题,但是我自己也提心吊胆的不知道行不行,接着我开始书写电梯主要的代码逻辑,这个时候变量的多以及对于构造方法的不熟练就让我吃尽了苦头,每一行代码的书写都让我绞尽脑汁,即使是对着第一次代码来修改一些变量名都让我痛苦不堪。最后写完一运行,得到的结果居然是无限循环去打印乘客的第一行请求。后来又陆陆续续的修改了几次,得到的结果都不尽如人意。甚至星期二(截止的那天)晚上选修课我也拿去修改。最后还是于事无补,失败了。
感悟以及收获:
从这次的题目,我深深的感觉到了我自己对于对象,类之间的运用不自如的问题,同时也让我的骄傲大大削减,以前觉得没多难,现在觉得还有很长的路要走,塞翁失马,焉知非福。或许这次的失败是为了下次更好的成功。
PTA第三次题目集
题目:
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。
电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
思考:
这次的修改主要是修改了请求的内容,同时这样里面的逻辑也会大大不同。我自己也没有太多的思路,毕竟连上一次都没写出来
最后,我输的一塌糊涂,下面是我的我的作业存在的问题、心历路程、感悟
拿到第三次作业时,我盯着"加入乘客类"和"取消乘客请求类"的要求反复看了很久。表面上只是类结构的调整,但实际要改动的底层逻辑却让我倒吸一口冷气。特别是看到外部请求格式变成<源楼层,目的楼层>时,我握着鼠标的手心已经开始冒汗——这相当于要把整个请求处理流程打碎重组。
刚开始新建Passenger类时还算顺利,但当我试图将原有的请求队列拆分成内部/外部队列时,突然发现Controller类的调度逻辑像被搅乱的毛线团。原本用方向判断请求优先级的逻辑完全失效,现在要根据源楼层和目的楼层的关系重新设计电梯的移动策略。我机械地敲着键盘,把原本的direction判断改成楼层数值比较,但电梯总会在处理完第一个请求后就陷入静止状态,仿佛在屏幕上无声地嘲笑着我的代码。
最痛苦的时刻出现在处理请求传递时。按照新规则,外部请求处理完后要把目的楼层加入内部队列,这个看似简单的需求让我在Elevator和RequestQueue两个类之间反复横跳。我创建了三个不同的队列对象,又在Controller里写了无数个getter方法,但队列里的请求还是没达到预想的效果。Elevator类里混杂着队列处理逻辑,Passenger类承担了本属于Controller的调度判断,而RequestQueue的过滤方法在重复请求时总会漏掉最后一个有效指令。我绝望地删掉所有新建的类,试图用第一次作业的代码强行改造时,IDEA里密密麻麻的报错让我彻底崩溃。
感悟:
这次惨痛经历像记重锤敲碎了我对面向对象编程的肤浅认知。我意识到类不是简单的代码容器,而是承载特定职责的智能单元。当Passenger类被迫处理电梯移动逻辑时,那种违和感就像让我窒息。而我只能在一旁干瞪眼着急。在尝试拆分职责的过程中,我常常陷入"该不该在这个类添加方法"的纠结,这暴露了我对系统边界的理解模糊。真正的单一职责,需要站在整个系统运转的高度来审视每个类的存在意义。
虽然最终没能完成作业,但在无数次的调试中,我意外领悟到了消息传递的精妙。当电梯需要响应某个请求时,不应该是某个类直接修改全局状态,而应该通过明确的调用链层层传递。这种认知的觉醒,或许比完成作业本身更有价值。编程之路道阻且长,但每次失败的尝试,都在为未来的成功铺就台阶。
踩坑心得
关于这次的作业,我只有第一次成功了。所以基本上没有太多的踩坑心得,因为没到达有坑的地方。但是也踩了,所以还是简单的讲一下:
1.第一次的Elevator类里塞了请求队列、状态判断、移动逻辑,导致一个方法修改了currentFloor后,另一个方法还在用旧值计算方向。比如在processInternalRequest()里删除了已处理的5层请求,但checkDirection()方法仍认为需要继续上行,结果电梯卡在5层空跑。
2.当电梯在3层收到<5>的内部请求时,代码直接用targetFloor > currentFloor ? UP : DOWN设定方向,却没考虑停在5层后若有新的<3>请求,电梯会突然反向导致抖动。后来在run()方法里加了if (direction == IDLE) reCalculateDirection()才勉强修复。
3.电梯在开门状态(state=OPENING)时,如果突然收到新的请求,processRequest()方法会立即修改state=MOVING,导致开门动画还没输出完就跳转到移动状态。最后在状态变更处加了互斥锁,并引入isProcessing标志位才解决。
总结
这三次的迭代作业,我学习到了很多:首先是对类和对象的使用的理解加深,我明白类和对象不仅仅是单纯的创建关系,更是一层一层的互扣关系。然后便是告诫了我自己不要轻视这门课程,不亏为java,世界上最火的几门语言,确实是很有难度的,需要我后面花费大把的时间来进行学习。另外还有非常只要的一点就是不懂就问,不要不好意思,因为世界上99%的问题都有前辈遇到过,只要自己去问去学,就不可能不会,虚心向他人请教是成为强者的必修课。有志者,事竟成!!!
对于课程和题目的建议:
希望老师在第一次的题目可以多设计几个测试点,不然大把分数连低保都拿不到真的很坐牢,另外多个测试点也有利于我们学生自己去判断学习应该要怎么样修改代码。谢谢老师。