一、前言
第一次blog作业,对PTA三次题目集的电梯题进行分析和总结,第一次的电梯题运行后与老师所给的测试数据完全匹配,但就是过不了测试点,后来在第二次的电梯题给了参考类图,就有了很明确的方向,然后再回头看第一次写的,好像就是没有考虑某个队列为空的情况。
题目集5:
进一步熟悉类的声明、创建与使用方法,类的构造方法的定义与使用方法,类的成员变量、成员方法的定义与使用方法,还有熟悉 正则表达式 。都是单类设计,题量中等,难度中等。
题目集6:
了解类间关系,根据所给的类图来设计多个类,设计类时必须遵循 单一职责原则(SRP) ,这次的电梯题要对第一次电梯题进行迭代。题量较少,难度中等。
题目集7:
类设计的数量比上次多,关系也更加复杂,不仅要了解一种求圆周率的新方法——蒙特卡洛仿真方法,还要对电梯题再次进行迭代。题量较少,难度中等。
二、设计与分析
第一次电梯
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>
,其中,乘梯方向用UP
代表上行,用DOWN
代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
PowerDesigner生成的类图
SourceMonitor生成的报表
题目中电梯的主要逻辑就是LOOK算法,两个请求队列:电梯外部请求队列(分上楼和下楼)和电梯内部请求队列,然后找最近的同方向上的头部请求,如果是外部请求还要看要去的方向是不是和电梯同向(这里是最让我烧脑的),而且还要注意方向的变化,本题中的电梯每到达了一个外部请求楼层,电梯运行方向要变成请求方向,然后getNextTarget()又是根据方向来确定下一个目标楼层的。
getNextTarget()方法(也就是后面的getNextFloor()方法)的逻辑是:
电梯不动时,找最近的请求(找到后由于电梯的方向发生变化,会再次寻找目标楼层);
电梯向上时,首先找在上面的内部请求或上面的上楼的外部请求(二者都有找二者最近的,一者没有返回另一者,下面同理),没有就找上面的下楼请求(这里会调转运行方向),再没有就找下面的内部请求或下面的下楼请求;
电梯向下时,首先找在下面的内部请求或下面的下楼的外部请求,没有就找下面的上楼请求,再没有就找上面的内部请求或上面的上楼请求。
这次电梯是单类设计,外部请求队列的设计是用一个LinkedList<int[]>对象来存储外部请求,int[]存储两个值,一个是楼层,另一个是方向(1表示上楼,-1表示下楼)。
可以看到平均深度(Avg Depth)和最大复杂度(Max Complexity)都远远高出正常值,主要还是processRequest()调用了太多次getNextTarget()方法,而getNextTarget()里又有多层if-else嵌套(就是为了找同方向上的最近的头部请求)。
第二次电梯
输入输出与上次相同
这次迭代是把电梯类拆成多个类以解决电梯类承担太多职责的问题,这次有老师给的参考类图,要完成的目标也就清晰许多,新增需求:
1.忽略高于最高楼层数或低于最低楼层数的乘客请求;
2.出现连续的相同请求只视为一个请求。
PowerDesigner生成的类图
SourceMonitor生成的报表
这次电梯要设计电梯(Elevator)类、乘客请求(RequestQueue)类、队列(ExternalRequest)类以及控制(Controller)类,这里也熟悉了单例模式的设计,主要是双重检查锁定模式(DCL)。说一下怎么应对新需求的,忽略高于最高楼层数或低于最低楼层数的乘客请求这个很简单,用个if-else就可以了。主要是出现连续的相同请求只视为一个请求,我用了个很基础的方法,就是和用指针处理C语言里链表的节点一样,定义一个p和q,让p在链表上走,q始终是p的前一个,检查p和q存储的内容是否一样,一样就移除p的内容,p再继续往后一个,依次下去直到末尾,然后又因为外部队列和内部队列不一样,外部有方向,所以这方法又用了一遍(没区别,就是加了方向的检查)。
和上次一样,平均深度和最大复杂度不但没有下降,反而还上升了,因为核心逻辑在getNextFloor()方法内,而我也不知道应该怎么样才能减少if-else的嵌套,还需通过逻辑拆分、设计模式优化和严格注释规范提升可维护性。
第三次电梯
这次迭代是加入乘客类(Passenger),取消乘客请求类,新增需求:
1.在乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>
修改为<请求源楼层,请求目的楼层>
,其中,请求源楼层
表示乘客发起请求所在的楼层,请求目的楼层
表示乘客想要到达的楼层;
2.对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>
中的请求目的楼层加入到请求内部队列(加到队尾)。
PowerDesigner生成的类图
SourceMonitor生成的报表
passenger对象相当于是一个节点,这个节点存储两个东西,一个是请求源楼层,另一个是请求目的楼层,外部乘客请求是二者都要有,内部乘客请求是只存储请求目的楼层,请求源楼层设置为空,由此创建好passenger对象后在添加到链表里。
当电梯到达某一楼层时,会检查该楼层是否为外部头请求的源楼层,若是,电梯会更改运行方向为外部头请求的请求方向(源楼层 < 目的楼层为UP,源楼层 > 目的楼层为DOWN),然后把外部头请求的源楼层设置为空(这样外部头请求就只剩目的楼层),把外部头请求的内容加入到内部请求队尾,再删除外部头请求。
电梯的改良版本
此版本是第三次电梯提交后发现有许多不合理的地方,所以在此改良。第三次电梯没有过滤连续重复的请求,但是都能过测试点(可能因为第三次主要不是测这个),但由于这是需求,所以再次添加进来,也对电梯的核心逻辑进行了重构,以提升代码的可读性、可维护性和可扩展性。
PowerDesigner生成的类图
SourceMonitor生成的报表
从网上搜索到一些减少if-else嵌套的方法,有卫语句、策略模式、状态模式,这里也用到了卫语句、一点策略模式和状态模式(没有接口和具体类)。
可以看到深度和复杂度有了很大幅度的降低,最深层也只有4层,主要还是把getNextFloor()方法拆成了多个方法(这里方法有点太多了,之后改良的话应该会单独做个类),以让代码提前返回。
三、采坑心得
第一次的电梯题目没有老师给出类图,就好像没有设计师给出图纸,自己一个人完成画图和施工。而且一开始老师还要求不能用集合框架,要求只能用数组或链表完成,一开始想着用数组来做,用正数表示上楼请求,用负数表示下楼请求,通过检查输入字符里有没有UP或者DOWN来区分内外请求,但是做着做着,这里加个绝对值,那里加个绝对值,搞得自己都分不清一开始是上楼的还是下楼的,无奈转使用链表,但又要求不能用集合框架,这不就要我们自己去设计一个LinkedList类吗,然后题目又要求单类设计,鹅滴神啊,处处受到限制。后来老师说可以用LinkedList,就自己设计了一个内部的OutQueue类(虽然不符合要求)来存储外部请求,然后用LinkedList
到了第二次根据老师的类图把第二次的写出来后,想改成符合第一次要求(单类)的,也在网上找了一些办法,才想到用LinkedList<int[]>对象存储外部请求来写出此题。
第二次有了老师给的类图就很轻松地写出来了,但写出来的程序并没有严格按照老师给的类图来设计,因为电梯要根据下一个目标楼层移动,而很多方法都没有参数,没有参数就要在方法内再确定目标楼层,但每次移动后目标楼层都会改变,这就导致每个方法中的目标楼层都不一致,所以改了一下,传了一个目标楼层的参数进入方法内。然后根据测试数据来分析具体逻辑是怎样的,搞得差不多后提交上去就发现有个测试点是非零返回
然后就再仔细检查了一遍,是没有考虑某个请求队列为空的情况,在寻找下一个目的楼层时先检查一遍某个队列为空的话,返回另一个队列的头部请求即可。
第三次倒是没踩到什么坑,稍微改改提交上去竟然就对了,但是代码没有过滤连续重复请求,而且可读性和可维护性还是不够好,所以改良版本就在第三次的基础上进行了重构。这不重构还好,一重构就出问题了。一开始只是想单纯的把过滤连续重复请求的部分粘贴上去,然后就发现代码长度超过PTA的限制(大概是450行左右),那这不能不要过滤啊,就把单例模式的方法暂时删除了,可还是超过限制,就必须把getNextFloor()方法进行拆分,这里也花费了挺长时间的,不过好在也算是弄出来了。
四、改进建议
1.删除目标楼层请求的条件不够严谨,可以考虑在乘客类中添加一个int的targetFlag属性来确定是否为目标楼层来删除
2.功能新增:最小楼层为负数(考虑0层合理性)
3.改良版本的Controller类的方法过多,有点不遵循单一职责原则,可以考虑再添加一个类
4.深度和复杂度远远超过正常值(已在改良版本中解决)
五、总结
做工程还是要有图纸才能做,软件工程也是一样,要有类图才能写出具体代码。万事开头难,自第二次有了类图后,把核心逻辑理出来了,之后的问题就迎刃而解了。类的设计也要遵循单一职责原则。这次的题目集让我学到了LinkedList<int[]>的用法、单例模式的应用,也接触到了卫语句、策略模式、状态模式等减少if-else、switch嵌套的方法。但是提高程序的可读性、可维护性、可扩展性的意识还不够好,在做题的时候,很多时候都没有把所有可能的情况都考虑到,也是在提交后才知道哪里缺少了什么。
建议:教师讲课的时候,绝大多数时间都是按照一个大纲来讲,根据大纲想到什么讲什么,而不是一步一步按照书上来讲,这对我来说就有点不太友好,毕竟一下子就不知道讲到哪里了。作业的话,具体测试哪些地方应该要有。实验能做出一个系统来也很不错,但是代码一个一个打字上去有点费时间,也很理解老师希望我们通过这种方式来提高打字速度的想法。