第一次作业-单部多线程可捎带电梯
本次作业采取了生产者-消费者模式进行设计,主要构造了输入器,调度器,电梯三个类,其中输入器为生产者,不断向托盘也即调度器发送请求。而电梯同时也不断向调度器获取请求。当输入器不再获取输入且调度器中的请求队列为空时,并且电梯将接送的乘客均送到目的楼层时,电梯停止运行。在本次设计过程中,调度器实质功能较少,基本上只具有一个共享队列的功能,主要的运行策略及捎带策略均集成到了电梯的内部。其中捎带策略选取的是标准的ALS策略。本次设计类图如下:
第二次作业-多部多线程可捎带调度电梯的模拟
本次作业相较于第一次作业在开始时引入了多部电梯并行运行。因此若是仍然是采用第一次的生产者-消费者模式,让电梯不断地向调度器请求请求,则失去了其调度意义。因此本次作业采用了生产者也即输入器不断向调度器发送请求,而调度器不断向各个电梯分派请求。而电梯中存在两个队列:要去接的乘客队列和正在送的乘客队列。由调度器分派任务后,请求进入电梯中要去接的乘客队列,在电梯到达特定楼层时经过判断后要去接的乘客进入电梯内,实现接送。本次相较于第一次作业而言,电梯内部保持了较高的功能独立性,同时在电梯内部的运行算法方面也进行了进一步的优化,将分派任务与执行任务分配给调度器和电梯内部,实现了一定程度上的高内聚性。本次设计类图如下:
第三次作业-多部多线程可捎带调度电梯
本次作业相较于上一次作业,存在如下难点及拓展:①电梯可以运行的楼层受到限制②在运行过程中,电梯可以动态增加。针对这两个拓展,分别采取解决方案如下:①由于电梯可以运行的楼层受到限制,因此必须引入换乘机制,也即乘客可能会乘坐多部电梯,经过换乘后才能到达目的地②针对动态增加机制,在调度器中引入了线程池,将电梯均存于调度器中,由调度器唤醒。在这次设计过程中,很好地实现了面向对象思想中的迭代思想,主体部分均是基于第二次作业,仅在较少的地方进行修改,其余均为基于第二次作业的拓展功能。本次设计仍然保持了电梯内部运行功能的高度集中性,但调度算法采取了一定的修改,采用了一定程度上的贪心算法,也即尽量减少换乘的次数。同时考虑到了乘客可能换乘的情况,引入了Person类,将乘客分为了可以直达的乘客和需要换乘的乘客,需要换乘的乘客在进入电梯时设置暂定楼层也即换乘楼层,在到达换乘楼层时乘客离开电梯,同时将其发往调度器,由调度器将其分配给接送他的电梯。本次作业的类图如下:
方法复杂度如下:
Controller.addElevator(Elevator) | 1.0 | 1.0 | 1.0 |
Controller.Controller() | 1.0 | 1.0 | 1.0 |
Controller.InputNotEnd() | 1.0 | 1.0 | 3.0 |
Controller.Put(Person) | 7.0 | 6.0 | 7.0 |
Controller.putElevator(String,String) | 1.0 | 4.0 | 4.0 |
Controller.putPerson(Person) | 1.0 | 1.0 | 1.0 |
Controller.putRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
Controller.randomPut(Person) | 1.0 | 1.0 | 1.0 |
Controller.runAll() | 1.0 | 2.0 | 2.0 |
Controller.setKeepInput(boolean) | 1.0 | 1.0 | 1.0 |
Elevator.checkFull() | 1.0 | 1.0 | 1.0 |
Elevator.checkOpen(int) | 7.0 | 4.0 | 7.0 |
Elevator.checkOpenAble(int) | 3.0 | 2.0 | 14.0 |
Elevator.Elevator(Controller,String,String) | 1.0 | 3.0 | 4.0 |
Elevator.getCurrentFloor() | 1.0 | 1.0 | 1.0 |
Elevator.getDirection() | 1.0 | 1.0 | 1.0 |
Elevator.getTargetFloor() | 1.0 | 1.0 | 1.0 |
Elevator.keepRun() | 1.0 | 2.0 | 2.0 |
Elevator.move() | 1.0 | 2.0 | 3.0 |
Elevator.open() | 1.0 | 2.0 | 2.0 |
Elevator.outAndIn() | 7.0 | 10.0 | 12.0 |
Elevator.putRequest(Person) | 1.0 | 2.0 | 2.0 |
Elevator.run() | 1.0 | 7.0 | 7.0 |
Elevator.setDirection() | 1.0 | 1.0 | 3.0 |
Elevator.setRun() | 1.0 | 13.0 | 13.0 |
InputDealer.InputDealer(Controller) | 1.0 | 1.0 | 1.0 |
InputDealer.run() | 3.0 | 5.0 | 6.0 |
MainClass.main(String[]) | 1.0 | 1.0 | 1.0 |
Person.getCurrentFloor() | 1.0 | 1.0 | 1.0 |
Person.getFromFloor() | 1.0 | 1.0 | 1.0 |
Person.getPersonId() | 1.0 | 1.0 | 1.0 |
Person.getTempToFloor() | 1.0 | 1.0 | 1.0 |
Person.getToFloor() | 1.0 | 1.0 | 1.0 |
Person.Person(PersonRequest) | 1.0 | 1.0 | 1.0 |
Person.setCurrentFloor(int) | 1.0 | 1.0 | 1.0 |
Person.setTempToFloor(int) | 1.0 | 1.0 | 1.0 |
Total | 58.0 | 86.0 | 111.0 |
Average | 1.6111111111111112 | 2.388888888888889 | 3.0833333333333335 |
类复杂度如下:
Controller | 1.8 | 18.0 |
Elevator | 3.7333333333333334 | 56.0 |
InputDealer | 3.0 | 6.0 |
MainClass | 1.0 | 1.0 |
Person | 1.0 | 8.0 |
Total | 89.0 | |
Average | 2.4722222222222223 | 17.8 |
就本次架构的可拓展性来讲,由于保持了电梯运行的高度独立性,因此可以在其后引入新型电梯,同时可以增加更多的电梯。而就调度器而言,也可以采用性能更佳的调度器,而无需修改电梯内部的运行逻辑。
BUG分析
对于第一次作业而言,由于采用了while循环轮询的方式,当调度器一直没有接收到新的请求时,电梯没有采用wait操作,这就导致了在互测中产生了CPU超时的问题。
对于第二次作业而言,由于没有弄清楚System.in的机制,在主线程和输入器中均新建了System.in,这就导致有部分数据没能进入输入器,最终也导致了没能进入互测阶段。
对于第三次作业而言,出现了部分乘客在换乘后没能正确被接送到目的楼层的情况,后经分析发现是当乘客到达换乘楼层后所有电梯都关闭了的原因,因此在其后引入了translate标签,仅有换乘的乘客为0的情况,且满足之前的停止情况时电梯才会停止运行。
查找BUG策略
在查找BUG过程中,使用了python的os库和subprocess库,由subprocess库模拟输入。由于在测试数据上,仅仅只做了基本的功能性测试,因此没有发现过别人的BUG
对比和心得体会
在这三次作业中,更多地体会到了面向对象的编程思想。在设计时也更多地考虑到了一定的可拓展性,其中尤其是从第二次作业到第三次作业,并没有进行过多的修改,而只是基于原有构架的拓展,同时对于设计模式的六大原则,在设计过程中也有一定的考量,因此通过这几次作业仅就面向对象编程来讲还是有了更深入的理解。同时对于多线程编程,通过这几次的作业包括实验课,学会了较为常见的一些模式,如观察者模式,生产者消费者模式,以及Worker-Thread模式。对于加锁,解锁,以及多线程同步问题有了更为深入的理解。但同时在这次设计过程中也存在着一定的不足,例如没有在调度算法方面进行进一步的优化。但通过这几次作业,也获取到了不少的知识。