黑马程序员技术交流社区

标题: 【上海校区】BUAA OO Unit2 电梯调度 [打印本页]

作者: 梦缠绕的时候    时间: 2020-4-24 09:01
标题: 【上海校区】BUAA OO Unit2 电梯调度
这次作业完成了一个开环可选层电梯调度系统。第二次迭代加入了容量限制、多部电梯,第三次迭代加入了电梯楼层分工、增添电梯请求。
1. 系统架构Requests
Notify
Update
Create
Check
Operate
Adapt
MainClass
Schedule
Executor
Elevators
Method

2. 同步控制定时控制
同步控制的主要由Schedule设定,由Executor执行。并发控制的核心为一个阻塞定时监听器,可实现可调整的定时控制。这个模块的实现方法参考了java.utils.Timer。
private long scheduledTime = Long.MAX_VALUE;public synchronized void retrieveNextAction() throws InterruptedException {    long curTime = System.currentTimeMillis();    while (curTime >= scheduledTime) {        wait(scheduledTime - curTime);        curTime = System.currentTimeMillis();    }    scheduledTime = Long.MAX_VALUE;    return;}
另外,除Schedule外,另一个共享变量为Elevator。这里Elevator类仅用作记录电梯状态,提供改变电梯状态的接口。所有操作由Executor根据Schedule产生,并在敏感操作(如关门-移动序列)的执行过程中进行了上锁,屏蔽Schedule类的所有请求,保证一致性。
对于电梯移动,在Executor中对Schedule上锁保证安全性。对于人员流动,使用ConcurrentLinkedList定义可执行队列保障安全性,并在电梯移动时清空。Executor请求完成后调用相应方法通知Schedule,根据策略更新预期动作。
UML协作图 (Sequence Diagram)
* 协作图中动作可能不满足正确性,但反应了动作之间的时序关系。
* 2个线程为MainClass主线程和Executor执行线程。Schedule、Elevator为共享变量,被动进行数据调取。
* 还存在Schedule-Elevator之间的调用。
同步控制相关版本迭代3. 架构设计
本节以第三次作业的版本为准讨论了架构设计和可扩展性。
需要实现2个接口:IElevator、OperationMethod。
第三次作业通过继承的方法已经实现了楼层限制电梯,以及针对3中楼层限制电梯的方法不同方法。
功能-性能平衡
由于采用了策略与调度分离的架构,使得在实现相应功能的同时,为策略的制订保留了很大的空间,同时允许对不同的策略进行试验。但在策略制订的过程中,可扩展性做的不是很好,功能的调整往往需要策略做出较大的调整,在前几次作业中均进行了策略重构,可能也与使用的策略具有较高的特殊性有关。
准则异常
单一责任准则:Schedule类和Executor类是所有请求实现的关键路径,对进一步添加请求不理想,应考虑建立请求接口,将请求执行步骤抽象为【电梯操作-调度更新-时间等待-新的请求】。
接口隔离:应考虑将策略接口拆分成发送策略接口、接受数据改变接口。
需求扩展方法
对于后续的扩展,可以根据扩展的类型进行版本迭代。
4. 度量分析类复杂度(Weighed Method Complexity)第一次作业第二次作业第三次作业
第一次作业中对2种策略进行了测试,最终选用了略微优化的Als策略。另一种策略过于复杂而且效果不是很好。第二次作业中对ALS策略进行了调整,以适配多电梯,效果类似于LOOK策略,但实现后导致MethodAlsMultiple复杂度过高。第三次作业将策略分成主策略和子策略,类复杂度可以接受,MethodAlsMultiple基本未作修改。
方法复杂度(Cyclomatic Complexity)第一次作业第二次作业第三次作业
这几次作业设计中Executor线程需要完成全部动作的发送,工作量较大,一开始放在了run方法,尽管后来做出了一些步骤的提取,仍旧有很多步骤被留在了run方法中,导致复杂度较高,这个是不恰当的。
另外,在第三次作业中,部分优化方法直接进行了重复逻辑的复制粘贴,在一个方法内引用了较多外部参数,还需要进一步重构。
循环依赖第一次作业第二次作业第三次作业
第一次作业由于分包不太恰当,导致根目录和子包出现了循环依赖,同时策略类和调度器也存在循环应用。第二次作业调整了Timer类的分包,建立了策略类和调度器的共有引用类,解决了这个问题。第三次作业由于子类实现了主类的内部接口,而主类有又持有子类的引用导致循环引用(所以根据规范应该把内部接口放在外部,或者编写额外的工厂类组装主类、子类)。
类结构图
5. BUG分析
第一次作业中,中测阶段的bug主要为题目理解不当,没有正确的使用输出包,强测互测没有发现Bug。
第二次作业中,强测互测阶段也没有发现Bug。
第三次作业中,强测阶段没有发现bug,但互测阶段发现了两个较为严重的bug。一个是电梯容量定义错误,然而在之前的中测强测中由于数据较为均匀、稀疏,加上C电梯利用率较少没有被测出来,自测时也没有再做检查。另一个是上电梯后,尽管已经通知了调度器更新,但在调度器更新函数的编写时没有考虑换乘问题,导致换乘时可能出现一个人上两个电梯的情况,在更新函数中加入一个删除相同请求就可以了。这个可能原因是前两次作业调度器编写时内联逻辑过多导致鲁棒性不高,环境改变后忘记了相关内联逻辑。
6. 测试简易评测样例定时读入类
使用自己编写的ScannerX实现了一个简易的定时输入,满足基本的测试需求。在test模块中,将main函数中的ElevatorInput替换为ScannerX即可测试。定时输入使用sleep进行定时,在第一次调用时初始化,根据当前时间进行修正。
测试策略
实际上第二第三次互测中均没有发现其他人的bug,可能是强测成绩比较好分到的屋大佬较多...第一次互测中由于定时输入没有做时间修正导致hack数据时间不准确,没有hack上。
和第一单元测试相比,这次测试评测机搭建更为麻烦一些,不像第一单元直接使用sympy计算就可以验证正确性,同时样例投放也更加困难一些。这导致测试的效率下降了一些。
7. 心得体会多线程程序
这次是第一次完整地编写多线程协同程序。在并发程序的编写中,需要仔细地考虑冲突问题,但如果对于每一个语句都进行考虑显然是不划算的。一方面是经典模型引入,例如读者、写者问题,生产者、消费者问题的解决方法,一方面是系统架构的独立性。如果将一个大的并发问题拆解成几个独立的链条,仅需考虑链条与链条的并发、链条内部的互访问,便可以“分而治之”,运用相关模型逐个击破。






欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2