第二组作业博客总结
OO第二模块作业总结
丁元杰 17231164
第五次作业(单电梯)
实现一个在连续楼层(都是正数)间运行的一个常速电梯,电梯容量无限大,对运行效率没有要求。
程序结构度量
第一次作业的类图如下:
代码度量如下:
时序图:
设计架构
需求分析
本次作业的设计重点在于如何保持各个类紧凑的同时,对未来可能的需求保留足够的可扩展性。一个设计的好坏,取决于它与需求的适配程度。因此,在本专题的第一次作业,对未来可能的需求进行一个估计是十分重要的。笔者最初有许多的设想,但是觉得有几个需求是一定会加上的(虽然现在说十分地马后炮):
- 多电梯(每个电梯可能不同)
- 高效的调度算法
- 电梯运行限制:限重和限制停靠
其一,对于多电梯的需求,我们知道了电梯必须单独成为一个类,负责存储它的各个属性,以及不断地sleep
以模拟电梯的运行。
其二,如果想要对调度算法保持扩展,那么所有涉及电梯运行决策的逻辑必须集中。同时,由于电梯的调度决策是实时的,电梯在运行的过程中必须能够时刻改变运行目的地。也因此,电梯既不能包含任何任务决策逻辑,也不能包含类似任务队列等可能阻塞调度实时性的机构。基于以上的考虑,我将电梯设计成了仅能执行“上楼、下楼、开关门”等原子操作的输出器。由此一来,所有的涉及调度的逻辑都集中到了调度器,因此调度器全知(知晓一切状态)全能(能够进行一切安排)。
输入器-调度器-电梯
由此我们得到了本次作业的三个主要的类:
- 输入器
ElevatorTest
- 调度器
Driver
- 电梯
Elevator
现在需要考虑多线程的问题,即这些类中,哪些是线程,哪些是可重入的方法集,哪些是互斥的过程。我在一番激烈的思想斗争后,决定将输入器和电梯设计成线程,而调度器是供电梯调用的工具。也就是:电梯在合适的时机请求调度器安排任务,调度器只有在被电梯调用之后才获取当前数据,并向电梯做出指示。那么什么时候电梯才需要调度呢?这里模拟真实环境,电梯不会在两层楼之间折返,因此电梯只需要在到达某一层楼之后才向调度器发出调度请求。
于是,整个程序的运行逻辑就变成了,电梯调用调度器,调度器查询输入器。其中第一个部分是互斥的(调度器代码不可重入),第二部分是一个典型的生产者-消费者模型。到此,再添加上ElevatorState
,MyPersonRequest
,ParaQueue
等几个用于在多个类之间传递信息的类,就完成了本次作业的设计。
对未来扩展的分析
最后分析一下为什么这样的设计是比较易于扩展的。
- 多电梯:多个电梯独立运行,互相不知晓对方的存在。每个电梯都在自己到达合适的楼层之后请求调度器,因此如果多个电梯同时请求调度器,会发生线程阻塞,但是对于程序正确性并没有影响,也不需要增加额外的代码。
- 更好的调度算法:调度器向电梯发出原子请求,因此在每一次电梯请求调度器的时候,可以把自身的运行状态告知调度器。调度器从而根据各个电梯的运行情况,以及当前所有未完成的任务的分布情况,来完成调度计算。无论这个计算有多么复杂,都只需要告诉电梯下一步的运行方向即可。
- 电梯运行限制:这些限制可以成为不同电梯的属性,在调用调度器的时候一并传递给调度器,再由调度器来决策当前是否可以上人,这个人是否能够乘坐此电梯到达目的地等。
个人认为这次电梯的设计是比较优良的,因此在之后的两次作业中依然保持了输入器-调度器-电梯的主轴。
调度算法
之前提到,本次作业所设计的调度器为各类调度算法预留了充足的扩展空间,因此可以较为轻松地实现出比傻瓜电梯更为优秀的调度算法。同时,由于取消了在电梯中的所谓任务队列,实现一个傻瓜调度算法的难度并不一定比一个效率更高的算法要优。于是,笔者模仿现实生活中的电梯,实现了一个拥有近似策略的调度算法。
目标楼层集驱动
目标楼层集指的是当前时刻所有电梯需要到达的楼层集合。对于等候电梯的乘客,他们的目标楼层是他们当前所在的楼层(电梯需要去那里接他们);对于电梯里的乘客,他们的目标楼层是他们的目的地楼层(电梯要送他们到那里去)。把这两类的目标楼层无差别的看待(因为都是对电梯的请求),并把它们组成的集合称为目标楼层集。
获得了目标楼层集之后,影响电梯下一步运行方向的楼层仅为目标楼层集中的最高楼层与最低楼层,电梯当前的楼层,以及电梯当时的运行方向。基本策略即为,如果电梯当前正在运行,且运行方向仍有目标楼层,那么就保持运行方向;如果当前运行方向没有目标楼层了,那么调转运行方向;如果当前没有任何目标楼层了,那么电梯原地等候。
暴力上下客
电梯到达某一层之后,如果电梯内有乘客的目的地是当前楼层,那么乘客下电梯;如果当前楼层有正在等候电梯的乘客,那么乘客上电梯。
调度算法性能
经过狭窄的随机数据测试,该方案的性能显著优于基于任务队列实现的电梯调度算法,即本次作业指导书描述的电梯调度算法。
自己程序的bug
在公测与互测阶段均未发现程序的bug。
发现bug的策略
本次作业使用了全自动测试(对拍)的方式进行bug筛查,不过不幸的是,没有找出任何人的bug 😃。
第六次作业(高效单电梯)
第六次作业要求完成一个支持楼层映射以及效率不慢于ALS的单电梯。
程序结构度量
第六次作业的类图如下:
代码度量如下:
时序图:
设计架构
经过分析,本次作业较上次作业的差别,仅为可达电梯楼层、0层楼的处理以及调度算法性能要求的区别。可以观察发现,本次的可达楼层依然是连续的,即,穿越相邻两个楼层所花的时间是恒定的。因此,可以借鉴诸多高级语言中对于时间变量的处理方式,存储与显示分离。就本次作业的电梯而言,可以建立一个从真实楼层(用于调度的连续编号)到显示楼层(输入输出约定的楼层)的一个映射,将所有设计映射的逻辑储存在单独的一个类中,在输入和输出之后模块中完成真实楼层到显示楼层的转换。
而由于本次作业并无电梯容量的限制,可以直接沿用第五次作业的调度算法。
自己程序的bug
在公测和互测中并没有发现任何bug。
发现bug的策略
在互测中使用了自动化测试脚本,成功定位了一位同学的电梯有可能冲出大楼的bug。
第七次作业(多电梯)
第七次作业要求实现三个服务楼层不同、载重量不同、速度不同的电梯。
程序结构度量
第七次作业的类图如下:
代码度量如下:
时序图:
设计架构
第七次作业的多电梯引入了对于电梯的限制,这些限制将被设为同一个电梯类的不同参数。本次作业只需要处理一个关键问题:电梯换乘。
本次作业采用了静态换乘的方案,也就是乘客的换乘方案与当前电梯的运行状况无关。由此,乘客的多次换乘可以被看作是多个连续发出的请求,只需要保证下一个换乘的步骤在上一个换乘步骤结束之后开始即可。因此,我选择改造MyPersonRequest
类,将其变为MyPersonRequestChain
类,使得每个人变成了一个请求序列:外部只能观测到最新的请求,在最新的请求被执行完成之后,调用nextRequest
方法,对象自动滚动到下一个请求。
自己程序的bug
在自行debug过程中,发现了电梯可能存在换乘中的同步问题。在换乘的上一个步骤执行完成之后,调度器调用乘客的nextRequest
方法,随后将该乘客的名字发布到电梯用于输出。由于乘客真正走出电梯的时刻是OUT
信息输出的时刻,所以前述的策略其实是破坏了乘客换乘的顺序。因此,增加一个调度器方法updateRequest
,供电梯在输出之后调用,专门用于更新执行完的任务的请求,保证其与主调度进程互斥即可。
在公测和互测中并没有发现任何bug。
发现bug的策略
使用自动化脚本进行对拍。虽然在公测中发现了房间中的不少bug,但是全部无法在评测机上复现:)
posted on 2019-04-23 11:34 No_CE_in_Vegetable 阅读(150) 评论(0) 编辑 收藏 举报