OO第二单元总结

Unit2总结


一、设计策略

第一次作业是单部多线程可捎带电梯,忽略了Tmax的限制,写了一个FCFS一看通过了所有中测就不管了,强测全部RTLE。

第二次作业要完成多部多线程同型号可捎带电梯。为了填坑首先实现采用look策略的单部电梯。look最大的特征是上下轮流扫描请求,试图在上行或下行途中完成尽可能多的请求。

首先想到调度器Dispatcher要实现为一个线程,电梯单纯作为一个被调度器指挥的工具,只需把移动、停靠、开关门等行为交给调度器调用即可,本身不必作为一个线程。则单电梯时一共有MainClass和Dispatcher两个线程,main从输入中读取请求传入调度器中的队列中,注意中间没有托盘,因此他们不构成典型的消费者生产者模式。

有几个关于实现的具体问题要仔细考虑,否则很容易在几种可能的实现途径之间徘徊不定,浪费大量时间。下面提供我的设计和想法仅供参考。

1.调度器指挥电梯要发出信息,这个信息的数据类型是什么?

这个问题决定了移动行为的实现方法,是处理下一个要完成的请求,还是根据一组楼层数的整数逐个移动并停靠。我的设计是调度器将一组要停靠的层数的arraylist传给电梯,每到达其中一层就在上下电梯的Map<层数curfloor, Queue<乘客id>>中查询一下,得到需要离开或进入的id队列。

OO第二单元总结_第1张图片

2.电梯要向调度器报告状态,那么这个update方法应该归为电梯还是调度器?

update方法是实现同步控制的关键,直接作用是让调度器获取电梯状态的更新,因此我选择在调度器中声明update,在电梯中的移动行为中调用。要注意在这个设计中电梯类不作为线程,调度器作为线程可运行。把电梯的引用作为对应调度器的成员变量,并不违反禁止在子线程中调用其他线程的原则。但电梯类成员变量中也包含了调度器的引用,是为了方便类间通讯,采取的一种非标准的作法。这么做不标准是因为极大地增加了类的耦合度,使类的关系变得模糊,在本电梯场景下之所以没有造成影响,是因为调度器和电梯一一对应,两者必须共同存在才能工作,所以稍微抵抗了耦合度的影响。

3.若采用look策略,update方法需要传递哪些信息,楼层,方向,是否停靠?

只需要传递所处楼层数即可,因为电梯要一批上行请求一批下行请求交替处理,运行方向呈现一定规律性,所以可以把灵活变化的运行方向固定为调度器可控的运行模式mode,如1为处理上行请求中,-1为处理下行请求中,0为空闲状态。当然这要配合电梯移动行为等整体性实现。

4.很自然地想到在调度器中维护两个队列upBuffer和downBuffer分别暂存上行和下行的请求,调度器应该何时扫描队列中的请求?

这是一个很容易回答的线程安全的问题,应在新投入请求和电梯改变mode时扫描。其中有一个细节,电梯运行中或空闲时都可能新投入请求,运行中投入要严格判断是否可插队(插队的实现与流水线转发完全相同),空闲时投入则可从容地安排处理顺序。

第三次作业的难点一是在换乘时不同线程的通讯,二是程序退出条件的判断。我选择了最傻瓜的换乘策略,低楼层在1层换,高楼层在15层换乘,维护一个换乘产生的新请求的缓冲队列,每当upBuffer和downBuffer为空时释放到控制中枢。

二、扩展性分析

1.单一职责原则 Single Responsibility Principle

调度器和电梯虽有相当的耦合度,但从外部来考虑,各类的分工比较明确,也比较符合现实认知。

2.开闭原则 Open Closed Principle

新增的需求有不同的电梯名,停靠楼层,电梯容量和移动速度。都可以通过在调度器和电梯类中新增或修改方法函数体中的静态变量实现。这需要了解具体的实现细节,所以扩展性很差,应该添加对应的修改参数的方法。

3.里氏替换原则 Liskov Substitution Principle

未涉及继承。

4.接口隔离原则 Interface Segregation Principle

未使用接口。可以选择自定义包含必须参数的新请求接口,实现功能更丰富使用更灵活的数据结构。

5.依赖倒置原则 Dependence Inversion Principle

不符合依赖倒置原则,控制中枢有一个匪夷所思的方法ReleaseBuffer用于获取各调度器由于换乘产生的新请求,这个行为是依赖于底层调度器的实现细节而产生的,违背现实抽象意义上的控制中枢行为。

三、程序度量

OO第二单元总结_第2张图片

OO第二单元总结_第3张图片

可以看到Dispatch类函数多且复杂,变成了一个超级类。一个原因是有些方法太复杂,就从面向过程的角度拆成了若干private方法,另外也是因为一个调度器要和一个电梯组合在一起发挥真正电梯的功能,不能在同一级别拆解调度器部件,为了符合常规认知便放弃了精简Dispatcher的工作。

四、bug分析

第二次作业未被测出bug但性能分很低,找到如下原因 1在同一层既要下人又要上人时,只能开门下关门再开门上关门,浪费了一个开关门的时间(已修复)。2实际容量小于荷载容量,因为电梯接受的是具体的行为指令,本身无法检测荷载人数,只能交给调度器控制,但update方法并未更新上下乘客的信息,只能在请求队列中静态控制(未修复),这是一个程序扩展性差的生动教训,虽然避免了第二三次作业重构,但受限于底层设计无法完全满足新需求。

第三次作业在互测中测出一个数据冒险处理不当造成的RTLE(超时了0.005s),电梯运行中的请求插队要避免电梯急停或连续开门上乘客的发生,但在转换模式时不需要考虑这些条件判断,只要把buffer中的指令串成一串即可(已修复)。

OO第二单元总结_第4张图片

五、心得体会

多线程程序调试确实很挑战,感谢讨论区同学的分享自己得以尝试了多种debug方法。我也借鉴了流水线cpu转发的设计,但也因为底层数据结构的选择上考虑不够,产生了一些优化起来极其困难的问题。尝试运用学到的消费者生产者、观察者以及Worker-Thread等多线程模式,对于锁的机制有了更深的认识。在后面的学习中仍要注意避免对过程和实现途径的过度关注,要相信抽象创造一个完善独立的且符合一般认知的个体,通过个体间的简单规则同样能能实现复杂逻辑。

你可能感兴趣的:(OO第二单元总结)