面向对象第二单元总结

面向对象第二单元总结

架构设计

由于本单元三次作业间的迭代关系,此处主要以第三次作业为例来展示电梯架构。

核心架构

本单元作业核心架构思想为通过按照楼层与进出类型划分的请求队列(FloorQueues)来沟通输入与电梯的运行。即工程的核心部分主要分为两个板块内容:一是输入调度,主要包含输入类(InputThread)与调度类(Scheduler);二是电梯,主要包括电梯类(Elevator)、电梯模块类(ElevatorModule)、电梯类型类(ElevatorType)与请求队列类(FloorQueues)。另有主类(MainClass),以及作为工具类的模拟电梯类(VirtualElevator)与工厂类(Factory)。

其中,电梯板块的功能如下:每一个电梯线程(Elevator)中包含一个电梯模块(ElevatorModule)成员,电梯的运行即基于电梯模块的状态。电梯模块中包含了电梯的属性与状态,如电梯类型(ElevatorType type)、电梯ID(String id)、电梯负载(int load)等,其中最为核心的成员属性即为请求队列(FloorQueues floorQueues)。请求队列按照进入或离开电梯将请求存储在两个楼层队列进入请求队列(in)与离开请求队列(out),每个楼层队列中将所有进入或离开的请求按照楼层划分为20个请求队列(ArrayList<PersonRequest>),电梯接收的请求首先会加入进入请求队列的对应楼层(from),并在相应进入请求(即乘客进入电梯)后,将该请求从进入请求队列加入的离开请求队列的对应楼层(to),当电梯到达该楼层时,便将离开请求队列清空。电梯线程仅负责电梯按照一定的规则(详见“电梯运行规则”部分)运行。

电梯运行规则

电梯的运行以每到达一个楼层为周期,周期内部包含一系列确定的运行步骤:方向设定(turn),乘客离开(getOut),乘客进入(getIn),电梯关门(closeDoor),线程等待,电梯移动(arrive)。

其中,方向设定为最关键,也是唯一影响电梯移动的步骤。方向设定的核心思想为,除极少数必要情况,一律不改变运行方向。极少数必要情况指,电梯的运行方向上已无其他任何进入或离开的请求,此时,电梯需要优先根据当前楼层的首个进入请求所要到达的楼层来设定方向,如果当前楼层也已无请求,则转向。

电梯以“先出后进”为原则,每到达一楼层确定方向后,释放所有要在该层离开的乘客,再在负载范围内接纳所有该层需要进入电梯且到达楼层与当前电梯运行方向一致的乘客,关门并移动。

如果电梯关门后当前电梯队列已无请求,则进入请求队列的等待池(wait)。

同步块与锁的设置

本单元三次作业中同步块与锁的选择基本一致。主线程仅包含两类子线程同步块,一是唯一的输入线程(InputTread),二是各个电梯线程(Elevator)。作为锁的仅有各个电梯的电梯模块(ElevatorModule)。即每当输入线程获取一个乘客请求(PersonRequest)时,静态的调度器(Scheduler)根据一定方法(详见“调度器设计”部分)决定将该请求分配给某一个电梯接收,输入线程获得该电梯模块(ElevatorModule)的锁,将请求加入其楼层队列(FloorQueues)后释放锁,唤醒电梯线程;每个电梯线程会在运行的每个周期开始时获得其电梯模块(ElevatorModule)的锁,并在周期结束时释放。

调度器设计

本单元三次作业调度器均为静态,调度器的唯一功能为确定将输入线程获取的请求分配给哪一个电梯来接收。电梯对于自己队列内请求的响应方式包含在电梯运行规则内,不属于调度器管理范围。

调度的核心原则为时间优先调度(Time Priority),该调度方式需要借由模拟电梯(VirtualElevator)来实现。思路为以待分配的请求(PersonRequest request)与所有电梯线程的模块(ArrayList<ElevatorModule> modules)为参数,通过模拟计算每一个电梯接收该请求后的剩余运行时间,确定剩余运行时间最短的电梯来接收该请求。

模拟电梯设计思路为将电梯线程的运行(run)静态化,即以接收请求后的电梯模块为模块,通过完全一致的运行规则,将所有的线程阻塞(sleep)以变量(time)记录时间总和来代替,直到请求队列为空后停止运行,返回总时间(time)作为模拟电梯运行的剩余运行时间。如果请求中涉及的楼层含有该类型电梯所不能停靠的楼层,则直接返回一个较大数(如999999)。

在输入线程中,仅需通过调用调度类的静态方法(static int timePriority),即可确定接收请求的电梯目录(index)。

可扩展性分析

由于本单元作业的电梯架构设计上坚持了抽象化、归一化、高内聚的原则,本次电梯设计具有较强的可扩展性。如从第一次作业到第二次作业时,仅需要创建多个电梯线程(Elevator)及模块(ElevatorModule),在通过一定调度策略来决定请求分配给哪一个电梯模块的楼层队列即可;第二次作业到第三次作业时,仅需在电梯模块内部修改部分参数,并增加包含可到达楼层的成员电梯类型(ElevatorType type),以及在模拟运行时将涉及该类型不可达楼层的请求运行时间直接设置为较大的数即可。

对于更多的需求,在进行电梯功能的扩展时,主要对电梯(Elevator)的运行规则,电梯模块(ElevatorModule)的部分成员函数与方法,以及调度器(Scheduler)的调度方法进行适当的增添、修改,即可解决大多数一般需求。

第三次作业UML类图

第三次作业UML顺序图

BUG分析

作业BUG分析

第一次作业中,对于ArrayList遍历的错误使用导致bug。由于乘客进入电梯时,需要遍历楼层的请求队列,并在接收一个乘客后会将其请求从队列中移除。本来此处使用遍历的语法,发现对于反复移除的队列此种语法会导致bug,更改为以i计数的标准语句后解决。

第二次作业与第三次作业均未发现任何bug。

发现BUG的策略

在寻找别人程序bug时,主要采用的策略依旧是通过大致浏览代码,着重查看关键部分,如线程安全、电梯调度策略、电梯运行规则等,来发现有可能出现bug的部分寻找测试样例针对测试。

这种方式相比盲目通过大量样例来测试理论效率要更高一些,尤其是在大家的程序相对完善的时候。但有时会由于耗费时间过多导致无法对所有别人的程序进行检查,并且当大家程序完善到一定程度时,很难通过检查源代码来发现bug。比如本单元第二次与第三次作业的互测,由于大家第一次作业已经经过了线程安全的考验,第二次与第三次作业的测试重点在于性能,通过浏览别人代码来发现bug收效甚微,再加上时间问题,反而不如通过大量样例来排查bug的效率高。

与第一单元的不同

本单元作业由于有了多线程的加入,对于程序bug的排查便有了“线程安全”这一方向,于是在检查别人bug时,可能会着重检查一下线程之间协作的问题。当然,在第二次作业与第三次作业几乎已经很难发现该类问题。

心得体会

本单元通过电梯系列的作业,学习巩固了多线程编程的方法。相比于第一单元对于工程型作业的不习惯,本单元从一开始便本着工程化编程的原则,将主要精力放在架构设计的层次性与逻辑的清晰性。于是,本单元作业从第一次作业开始就做到了拥有良好的层次逻辑与可扩展性,接下来的两次作业的完成也变得相当轻松,这全都得益于一开始便注重程序设计的各项原则。

当然,多线程问题仍然是本单元作业的重头戏。由于一开始对线程的概念的不熟悉,导致在程序设计之初线程的构造与协作成了主要问题,先后出现了线程无法唤醒以及程序无法结束等问题。但在通过查询相关资料,以及学习模范化的代码后,对线程相关也有了更深刻的认识,能够将线程精简化,并把主要工作放在电梯运行性能的优化上,最终得以完成相对精妙的设计。

相信在接下来的学习中,能够对于程序的层次化设计架构、线程的构造协作等相关内容有更加全面的认知与锻炼,将自己的工程化程序设计能力提升到新的高度。

 

posted @ 2021-04-27 21:43  月下天弦  阅读(82)  评论(0编辑  收藏  举报