第一次BLOG作业-电梯程序设计

一、前言:

由于前面的学习并没有写过类似的题目,只是上课学到了相应的规则和方法,第一次动起手来也是给了自己当头一棒。本次PTA的题目集5-7考察了单部电梯的运行程序,考到了数列,正则表达式,简易的look算法等,题量不算太大但是对于我这种Java初学者来说有需要思考四五天才能修改好程序。由于以前C语言都是简易问题,这几次作业要考虑整体设计,而且每一次都是进行类设计的迭代,完成度没有特别高,题目具有一定的难度。但是也提升了我们的基础编程能力和对面向对象语言的初步了解和认识。除了电梯以外的其他题目难度中等。

二、设计与分析:

第五次题目:

设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
SourceMontor生成报表:
Metrics Details For File 'Main.java'

Parameter Value
========= =====
Project Directory C:\Users\32306\IdeaProjects\shiyan1\src\experiment_1
Project Name shiyan1
Checkpoint Name Baseline
File Name Main.java
Lines 219
Statements 142
Percent Branch Statements 21.8
Method Call Statements 55
Percent Lines with Comments 0.0
Classes and Interfaces 5
Methods per Class 3.00
Average Statements per Method 7.40
Line Number of Most Complex Method 72
Name of Most Complex Method Elevator.determineDirection()
Maximum Complexity 14
Line Number of Deepest Block 199
Maximum Block Depth 5
Average Block Depth 2.21
Average Complexity 4.07


Most Complex Methods in 3 Class(es): Complexity, Statements, Max Depth, Calls

Elevator.addExternalRequest() 2, 2, 3, 2
Elevator.addInternalRequest() 2, 2, 3, 2
Elevator.closeDoor() 1, 3, 2, 1
Elevator.determineDirection() 14, 12, 3, 2
Elevator.Elevator() 1, 7, 2, 0
Elevator.getMaxExternalFloor() 4, 7, 4, 1
Elevator.getMinExternalFloor() 4, 7, 4, 1
Elevator.isValidFloor() 2, 1, 2, 0
Elevator.move() 8, 16, 4, 6
Elevator.openDoor() 1, 2, 2, 1
Elevator.processCurrentRequests() 5, 6, 3, 4
Elevator.processRequests() 5, 8, 4, 5
Elevator.shouldStop() 2, 5, 2, 3
ElevatorRequest.ElevatorRequest() 1, 2, 2, 0
Main.main() 9, 31, 5, 27


Block Depth Statements

0 7
1 24
2 64
3 28
4 17
5 2
6 0
7 0
8 0
9+ 0

类图:

解释和心得:
构思:最开始并没有完全考虑到电梯的运行逻辑,按照自己的想法进行设计,一直没写出来,然后在老师的提示下知道是look算法,然后通过设计电梯运行方向和外部请求的关系,判断下一步请求的选择,但是没有完全考虑情况,当电梯上行没有其他请求时不会自动转向,而且当需要转向时只考虑了内部请求,一概而论,所以出现了很多错误。
分析:通过MS的分析,程序有以下缺点:
1.代码里面重复率高,无用代码太多影响效率。
2.代码复杂度太高,嵌套太多,if else 语句使用频度过大,效率低下。
3.代码之间嵌套过多,单一职责原则应用有问题,难以修改和调试

心得:这次题目我自己并没有想出来逻辑,也是在同学的帮助下才搞懂,对于电梯运行有很多没关注到的地方,而且存在“一刀切”的不想思考的问题,对于某些判断没有深入思考,一概而论。
以后编写可以依靠idea的调试功能,更好的发现自己代码的逻辑问题。

第六次题目:

对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。
SourceMontor生成报表:Metrics Details For File 'Main.java'

Parameter Value
========= =====
Project Directory C:\Users\32306\IdeaProjects\shiyan1\src\experiment_1
Project Name tse
Checkpoint Name Baseline
File Name Main.java
Lines 217
Statements 145
Percent Branch Statements 19.3
Method Call Statements 68
Percent Lines with Comments 0.9
Classes and Interfaces 7
Methods per Class 2.14
Average Statements per Method 7.07
Line Number of Most Complex Method 88
Name of Most Complex Method Controller.processRequests()
Maximum Complexity 24
Line Number of Deepest Block 108
Maximum Block Depth 7
Average Block Depth 2.63
Average Complexity 3.60


Most Complex Methods in 5 Class(es): Complexity, Statements, Max Depth, Calls

Controller.closeDoors() 1, 1, 2, 1
Controller.Controller() 1, 2, 2, 0
Controller.moveToFloor() 3, 6, 3, 4
Controller.openDoors() 1, 1, 2, 1
Controller.processRequests() 24, 43, 7, 24
Elevator.Elevator() 1, 6, 2, 0
Elevator.printInitialState() 1, 1, 2, 1
ExternalRequest.equals() 5, 6, 2, 3
ExternalRequest.ExternalRequest() 1, 2, 2, 0
ExternalRequest.getDirection() 1, 1, 2, 0
ExternalRequest.getFloor() 1, 1, 2, 0
ExternalRequest.hashCode() 1, 1, 2, 1
Main.main() 9, 31, 5, 27
RequestQueue.addExternalRequest() 2, 2, 3, 2
RequestQueue.addInternalRequest() 2, 2, 3, 2


Block Depth Statements

0 10
1 29
2 36
3 34
4 15
5 11
6 6
7 4
8 0
9+ 0

类图:

解释和心得:
构思:这次题目增加了让添加控制类等多种类,更好的使用SPR原则,我调整了电梯的运行逻辑,把一些以前放在一起的方法分开,通过依赖等类间关系相互调用,但是在迭代的过程中出现了一些逻辑错误。
分析:
1.方法复杂度太高,含有太多的循环嵌套和分支。
2.耦合度太高,方法之间的调用次数过多。
3.有的方法太多复杂,有的方法太过简单,没有正常分配功能。
心得:使用的循环方法过于多,而且有些地方的循环存在遗漏,导致功能不完整。而且判断条件仍然存在问题,加了测试点就无法通过了,全凭借else if 语句修改也过于麻烦。下次再编写类似的题目时候,不要把逻辑全放入一个方法当中,出现问题难以查找,不方便进行下一次的迭代。而且还存在注释过少的问题,有时候自己都看不懂自己的代码,修改起来完全没有一点头绪,自己挖的坑到最后还是自己填。

第七次题目:

对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类
SourceMontor生成报表:
Metrics Details For File 'Main.java'

Parameter Value
========= =====
Project Directory C:\Users\32306\IdeaProjects\untitled5\src
Project Name 2
Checkpoint Name Baseline
File Name Main.java
Lines 244
Statements 179
Percent Branch Statements 21.8
Method Call Statements 67
Percent Lines with Comments 0.0
Classes and Interfaces 6
Methods per Class 4.17
Average Statements per Method 5.48
Line Number of Most Complex Method 107
Name of Most Complex Method Controller.determineNextTarget()
Maximum Complexity 28
Line Number of Deepest Block 129
Maximum Block Depth 6
Average Block Depth 2.82
Average Complexity 2.80


Most Complex Methods in 5 Class(es): Complexity, Statements, Max Depth, Calls

Controller.Controller() 1, 2, 2, 0
Controller.determineNextTarget() 28, 56, 6, 19
Controller.moveAndProcess() 9, 26, 4, 18
Controller.processRequests() 3, 5, 3, 4
Elevator.Elevator() 1, 4, 2, 0
Elevator.getCurrentFloor() 1, 1, 2, 0
Elevator.getDirection() 1, 1, 2, 0
Elevator.getMaxFloor() 1, 1, 2, 0
Elevator.getMinFloor() 1, 1, 2, 0
Elevator.moveDown() 2, 2, 3, 0
Elevator.moveUp() 2, 2, 3, 0
Elevator.setDirection() 1, 1, 2, 0
Main.main() 7, 22, 5, 16
Passenger.getDestinationFloor() 1, 1, 2, 0
Passenger.getDirection() 1, 1, 2, 0
Passenger.getSourceFloor() 1, 1, 2, 0
Passenger.Passenger() 1, 2, 2, 0
RequestQueue.addExternalRequest() 1, 1, 2, 1
RequestQueue.addInternalRequest() 1, 1, 2, 1
RequestQueue.hasExternalRequests() 1, 1, 2, 1
RequestQueue.hasInternalRequests() 1, 1, 2, 1
RequestQueue.peekExternal() 1, 1, 2, 1
RequestQueue.peekInternal() 1, 1, 2, 1
RequestQueue.pollExternal() 1, 1, 2, 1
RequestQueue.pollInternal() 1, 1, 2, 1


Block Depth Statements

0 7
1 35
2 54
3 30
4 11
5 24
6 18
7 0
8 0
9+ 0

类图:

解释和心得:
构思:这一次又增加乘客类,需要把请求转移存放在乘客类中,修改了输入出现了一些新的问题,因为没有了外部请求的方向,需要自己判断外部请求是DOWN还是UP,还有如果内外请求在同一层的话,需要修改逻辑进行选择,电梯要停靠两次。而且外部的下一次请求要加入内部请求的队尾,说句实在话有点反人类了,但是确实减少了程序的工作量。
分析:1.这一次程序的else判断语句更多了,证明问题更复杂,也引入了更多新逻辑,还有修改空间。
2.Main函数承担了太多的功能,导致方法复杂度高,应该分担到其他方法。
心得:首先还是和上一次题目一样,适当加注一些注释有助于修改。还有在逻辑的判断方面,每一次迭代都是对自己的一次考验。如同以前没考虑过内部为空,还要思考的情况,这次问题有修改还要把内部条件填充。以及在同一个楼层要开两次门的情况如何处理,内外一上一下时候不能只考虑内部等情况。这次题目教会了我一个程序不是一蹴而就的,而是需要多次思考,测试,迭代而得到更完美的程序,当然我的程序还存在很大的问题,一点都不完美。

三、踩坑心得:

1.老师也在课堂上教会了一些编写代码的小技巧,比如学了正则表达式就应该应用,在电梯程序里面用正则表达式可以很方便的读取乘客的输入,直接加入list里面,比用spring读取输出好用多了。另外使用enum枚举可以更简便的记录电梯的运行状态,不用再复杂的的写出数组去表示电梯的状态。

点击查看代码
enum Direction {
    UP, DOWN
}
**2.我学习到了循环不代表能包含所有的可能。比如在第二次程序是,我写的代码为循环只要下一步请求不是空就进行move,但是每一次输出的时候都会忽略刚开始第一层的状态输出Current Floor: 楼层数 Direction: 方向,让我百思不得其解,因为明显的第一层属于请求不为空的状态。但是在进行调试后发现,循环是从第二层才开始执行的,于是乎我在代码中加入了floor的初始化为1,从而可以实现循环从第一层就开始,输出正确。这告诉了我写代码千万不能像当然,要合理的借助编译器这个工具,亲手去查看自己到底错在了那地方。**
点击查看代码
    private void moveAndProcess(int targetFloor) {
        int currentFloor = elevator.getCurrentFloor();
        while (currentFloor != targetFloor) {
            if (currentFloor < targetFloor) {
                elevator.setDirection(Direction.UP);
                elevator.moveUp();
            } else {
                elevator.setDirection(Direction.DOWN);
                elevator.moveDown();
            }
            currentFloor = elevator.getCurrentFloor();
            System.out.printf("Current Floor: %d Direction: %s%n", currentFloor, elevator.getDirection());
        }
        boolean doorOpened = false;
        Integer internalReq = requestQueue.peekInternal();
        if (internalReq != null && internalReq == currentFloor) {
            requestQueue.pollInternal();
            System.out.printf("Open Door # Floor %d%n", currentFloor);
            System.out.println("Close Door");
            doorOpened = true;
        }
        Passenger externalReq = requestQueue.peekExternal();
        if (externalReq != null && externalReq.getSourceFloor() == currentFloor) {
            requestQueue.pollExternal();
            requestQueue.addInternalRequest(externalReq.getDestinationFloor());
            System.out.printf("Open Door # Floor %d%n", currentFloor);
            System.out.println("Close Door");
            doorOpened = true;
        }
        if (!doorOpened) {
            System.out.printf("Current Floor: %d Direction: %s%n", currentFloor, elevator.getDirection());
        }
    }
}
**3.以前C语言的作业,以及Java前几次的作业都是相对来说比较简单的,所以一直都没有养成画程序图的习惯。但这次第一次做电梯程序的题目时候,想了老半天也想不出正确的逻辑,原地打转,简直是舍近求远。后来沉下心来去了解了一下scan算法和look算法,然后根据老师给的测试用例,在纸上画一画电梯运行的逻辑,才能够写出正确的代码。**
点击查看代码
    private Integer determineNextTarget() {
        if (!requestQueue.hasExternalRequests() && !requestQueue.hasInternalRequests()) {
            return null;
        }
        Passenger externalReq = requestQueue.peekExternal();
        Integer internalReq = requestQueue.peekInternal();
        int currentFloor = elevator.getCurrentFloor();
        Direction currentDir = elevator.getDirection();
        if (externalReq == null) {
            return internalReq;
        } else if (internalReq == null) {
            return externalReq.getSourceFloor();
        } else {
            int externalSource = externalReq.getSourceFloor();
            Direction externalDir = externalReq.getDirection();
            int internalFloor = internalReq;
            if (currentDir == Direction.UP) {
                if (externalDir == Direction.UP) {
                    boolean extAbove = externalSource > currentFloor;
                    boolean intAbove = internalFloor > currentFloor;

                    if (extAbove && intAbove) {
                        return Math.min(externalSource, internalFloor);
                    } else if (extAbove) {
                        return externalSource;
                    } else if (intAbove) {
                        return internalFloor;
                    } else {
                        elevator.setDirection(Direction.DOWN);
                        return Math.max(externalSource, internalFloor);
                    }
                } else {
                    boolean extAbove = externalSource > currentFloor;
                    boolean intAbove = internalFloor > currentFloor;
                    if (intAbove) {
                        return internalFloor;
                    } else if (extAbove) {
                        return externalSource;
                    } else {
                        elevator.setDirection(Direction.DOWN);
                        return Math.max(externalSource, internalFloor);
                    }
                }
            } else {
                if (externalDir == Direction.DOWN) {
                    boolean extBelow = externalSource < currentFloor;
                    boolean intBelow = internalFloor < currentFloor;

                    if (extBelow && intBelow) {
                        return Math.max(externalSource, internalFloor);
                    } else if (extBelow) {
                        return externalSource;
                    } else if (intBelow) {
                        return internalFloor;
                    } else {
                        elevator.setDirection(Direction.UP);
                        return Math.min(externalSource, internalFloor);
                    }
                } else {
                    boolean extBelow = externalSource < currentFloor;
                    boolean intBelow = internalFloor < currentFloor;
                    if (intBelow) {
                        return internalFloor;
                    } else if (extBelow) {
                        return externalSource;
                    } else {
                        elevator.setDirection(Direction.UP);
                        return Math.min(externalSource, internalFloor);
                    }
                }
            }
        }
    }
**4.判断条件一定要足够精确才能让代码逻辑正确。比如在第二次作业的时候,我在删除已经选择的请求时,直接floor==nextfloor,导致把后面的相同楼层也删除了,导致程序运行时候列表里面直接少了一个floor,后面修改了让判断条件变成只有list里面的first才会删除,才能够让逻辑正常运行。程序的判断是多方面的,不能简单的判断,而是要一步一步优化。**
点击查看代码
urrent = elevator.getCurrentFloor();
        Direction dir = elevator.getDirection();
        
        while (current != target) {
            current += (dir == Direction.UP) ? 1 : -1;
            elevator.setCurrentFloor(current);
            System.out.println("Current Floor: " + current + " Direction: " + dir);
        }
    }

四、改进建议:

1.写代码要带注释
写代码时顺手加注释,不能为了图方便就省略。重点给复杂的代码、循环、判断还有自己写的方法加注释,说说代码干啥用、为啥这么写。有了注释,以后复习、改 bug 都能省不少事儿。不然等到迭代的时候看都看不懂,就真是竹篮打水一场空了。
2.不要把多个功能都放在一个方法中
一个方法只干一件事,一定要符合单一职责原则。遇到复杂任务,拆成一个个小功能,分别写成不同方法。这样代码看着清楚,还能重复用,等到下一次迭代的时候,如果增加不同的类不用再把一个庞大的方法费半天力气拆解。
3.加强基础编程能力
把Java基础知识点好好记住,好多简单的函数都能调用,多写练习题巩固,毕竟期中期末考试不能作假。搞懂类、对象、继承这些面向对象的概念,学会用调试工具找错,遇到异常知道怎么处理,自己解决总比求着别人看这么长的代码好说。
4.在编写前先设计类,画流程图
写代码前先想想需要哪些类,每个类负责干啥。再画个流程图,把程序运行步骤都画出来,从开始到结束,理顺逻辑。想好了再动手写,能少走很多弯路,代码也更清晰。在脑子里面想了一万遍的逻辑,都不如在纸上画上一次发现问题快。

五、总结

1.这三次的题目,基本掌握了正则表达式的运用,枚举的运用,列表的运用,基础逻辑的判断,类的相互关系,方法的设计。
当然我认为最重要的是通过这三次的练习,我理解了面向对象编程的初步阶段,比如类之间的关系,依赖,组合等等,还有对复杂问题解决的经验积累,先思考,画图,再入手等等。
2.不过,我也发现自己还有不少不足。比如代码写得太乱,复杂方法没拆分,注释也几乎没写,导致代码不好看懂和修改。之后得重点改进这些问题,把代码质量提上去,而且要写更多的题让自己写起代码来不生疏,提高自己的基础编程能力。Java是我们软件开发方向不可或缺的关键能力,一定要培养好自己的能力,尽力学好这门课 。
3.同学之间合作不可或缺。以后进入工作也是成组的工作,一定要学会相互合作,单打独斗在现在的编程来说可谓是难如登天。而且互相检查代码、分享经验,还能发现自己没注意到的问题,一起进步。
4.对于以后课程上面的学习,随着难度越来越大,要进行预习和合理的复习。每次上课前花点时间看看新知识,提前了解大概内容,上课时就能更快跟上老师思路,抓住重点。课后及时复习,把当天学的代码多敲几遍,整理遇到的问题和解决办法。

posted @ 2025-04-20 21:46  24201109-许博帆  阅读(47)  评论(0)    收藏  举报