BUAA面向对象2022第二单元总结
总览
作业内容
本单元的主要作业内容为电梯调度,共有3次作业,为迭代开发。
第一次作业为纵向电梯调度。
第二次作业增加了横向电梯,但乘客均能一次到达目的地。
第三次作业加入需要换乘的乘客请求。
完成情况
我采用了生产者-消费者模式,通过调度器向各个电梯分配请求,各个电梯的请求队列互不相干。
第一次作业由于错误理解了ALS策略,导致强测有一半RTLE,改成look策略后通过(至今不知道ALS应该怎么写)
第二次和第三次作业没有出现错误。
性能分:我几乎没有进行任何的优化(懒得卷) 第二次和第三次强测分数为96和98
互测情况:三次作业 发起/受到hack 分别为 6/2 , 0/0 , 0/0 (第一次一刀6杀真的爽)
bug分析
第一次作业:电梯策略错误,导致可能接了不该接的人,以及还有一个输出不安全的错误
第二次作业:无
第三次作业:无
实现过程
总体思路
除了main之外共有n+2个线程,分别为输入线程,调度器线程,和每个电梯的线程。
有一个共享类,为候乘表类,共n+1个共享对象,为总候乘表和每个电梯的候乘表。
大体流程是:输入线程收到请求后往调度器里面塞(包括加电梯的请求,我是用负数id的乘客表示加电梯),调度器把乘客的请求塞到某个特定电梯(详见调度策略),策略类根据电梯里的人和电梯的候乘表告诉电梯应该往哪个方向走(详见电梯运行策略),然后电梯就按着这个接送乘客。
调度策略:乘客按照最少次数换乘的路线去走(其中横向电梯最多一次,换乘最多2次),如果同楼层有多个合法电梯,则优先使用上一次使用时间最早的电梯。(下面有代码)
纵向电梯策略:look策略,电梯会处理完与电梯运行方向同方向的所有请求再掉头。具体来说就是如果电梯是静止状态,那么会往请求多的方向移动(如果没有请求就继续静止),否则如果电梯中有人,就会往当前运行方向上移动,否则如果候乘表为空就变为静止,否则如果电梯运行方向上有出发请求,电梯就会往当前运行方向上移动,否则电梯会掉头。(下面有代码)
横向电梯策略:顺时针转,简单粗暴。(look会死循环,ALS懒得写)
UML类图
UML类图解释
除了接口的实现,我把联系紧密的类用直线连了起来。
MainClass:初始化,启动输入和调度器
Diaoduqi:调度器,负责向各个电梯的候乘表塞乘客请求,同时也能处理加电梯的请求。调度器中保存电梯信息和电梯的可达信息,所有乘客的换乘方案都是由调度器完成。下图为调度策略:
Passenger:乘客,有起点start,终点dest,和当前一段的终点end。在前两次作业中没有换乘,所以只需要start和end。第三次作业中调度器更新乘客的end,当乘客到达end并下电梯时,会更新start并塞回总表,直到start和dest相同。
HouchengBiao:候乘表,有一个总表是所有请求的队列,每个电梯也有自己的候乘表,调度器从总表取出请求,塞到某个特定电梯。一个乘客在同一时间只能在一个电梯的候乘表中。
BdElevator:横向电梯,第二次作业加入,其实可以和纵向电梯合并,但我怕出锅就没搞。
Elevator:纵向电梯,电梯按照策略的指示上下移动,并上下人。各个电梯独立运行,互不影响。
Strategy: 纵向电梯的策略,为look策略。
BdStrategy:横向电梯的策略,顺时针转,其实可以不要。
Output:线程安全的输出类。
UML协作图
UML协作图解释
不太会画协作图,可能有些问题。
总体流程上面说过了,我再拿乘客的角度说一下。一个乘客从input进来,加到调度器的总表。调度器从总表中取出,钦定他的路线和中转层并把他塞到一个电梯的候乘表里。电梯接到他之后把他送到中转层,再把这个乘客塞到总表,进行下一次操作,直到乘客达到终点,此时到达乘客数+1
关于避免轮询和合理结束线程:电梯在为空的时候会调用候乘表的checkEmpty方法,如果候乘表为空且fucked未标记,那么wait,如果fucked标记,则结束线程。调度器在会调用总表的checkAllEmpty,如果总表为空且(fucked未标记或到达人数不等于总人数)那么wait,否则结束线程。
复杂度分析
优点:各个类的功能明确,线程安全性好,可扩展性强。
缺点:有些类在实现起来的时候实在是过于丑陋,以至于写出了bug。还有一个问题就是没有合并横向和纵向电梯。
总结与反思
代码架构
这次我对自己的代码整体架构还比较满意。在第一次设计架构之后,后面两次作业都能在之前的基础上继续迭代开发。
关于互测
其实我并没有读别人的代码,只是随便扔了一个数据,然后就6杀了。
反思
第一次的bug属实有点离谱,当时过了中测就没再管了,应该多测试一下的。
一些建议