OO第二单元作业总结
设计策略
本单元作业最大的进步大概就是三次作业基本沿用了相同的设计架构,不再像第一单元一样每次作业基本都要重构才能实现相应功能,而是在前一次的作业基础上稍作修改即可。这一点我觉得很不错。
在设计第一次作业时,通过查看往年学长们的博客我了解到未来电梯可能会出现一部变为多部、并且只能停靠特殊楼层的情况。为了预留可扩展的空间,我选择创建person
类为将来的换乘做准备,并且将调度器设计为runnnable
以备将来把乘客请求动态分配给不同的电梯。不过考虑到第一次作业只有一部电梯,为了避免死锁风险我没有启用调度器。
我设计的多线程协同结构主要是通过两个(类)共享队列实现的。其中输入线程(主线程)与调度器共享输入队列,调度器和电梯共享等待队列。输入线程只负责不断把读取的请求放入输入队列中;调度器负责把输入队列中的请求,根据电梯运行情况分配给不同电梯的等待队列;而电梯只根据自己的等待队列按照LOOK算法进行运输即可。这样三个线程的功能就实现了完全的分离。
结束程序的时候,我采用共享标志位的方法。当输入线程结束时,改变标志位通知调度器;调度器分配完所有任务时,改变标志位通知所有电梯;电梯送完所有乘客后根据标志位的情况判断是否结束。单向传递信息使得逻辑十分清晰,没有出现死锁的情况。
可扩展性分析
- 单一责任原则
main
负责读入请求,scheduler
负责把请求分配给每个电梯, elevator
负责根据等候队列的情况运送乘客,Person
会自行选择换乘时前往的楼层和乘坐的电梯。基本上满足了单一责任原则
- 开放封闭原则
这一点做的还是不太好,可能对于这一点如何实现还是理解得不够。虽然三次作业中整体架构没有改变,但是涉及到具体的细节还是需要对于每个类做出微调。所以开放封闭原则没有满足
- 里氏替换原则
第三次作业中采用了继承的方式实现ABC三种电梯,并且将其直接用于elevator
的相关操作,实现了里氏替换原则。
- 接口隔离原则
因为作业中我没有使用新的接口,只有线程类继承了runnable
,所以不牵扯到这个问题
- 依赖倒置原则
我选择了elevator
作为抽象类,具体实现的ABC电梯则继承抽象类实现具体功能,也算是变相实现了依赖倒置原则。
基于度量的程序结构分析
三次作业中还是出现了比较多的“红色”复杂度的方法,主要是涉及到电梯的自我控制。这与我选择的设计策略有关,似乎也想不到什么办法可以把复杂度降低,因为电梯自主选择运行方向的时候确实会出现较多分支。
不过这种方法的好处是三次作业虽然功能逐渐增加,但是复杂度高的方法复杂度并没有继续上升:因为电梯的自我控制实现之后调度器只需要负责分配任务即可,所以总体来说还能接受。
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.Elevator(ArrayBlockingQueue |
1 | 1 | 1 |
Elevator.eleClose() | 1 | 2 | 2 |
Elevator.eleIn() | 1 | 3 | 3 |
Elevator.eleIsEmpty() | 1 | 1 | 1 |
Elevator.eleOpen() | 7 | 7 | 7 |
Elevator.eleOut() | 1 | 3 | 3 |
Elevator.go() | 1 | 2 | 3 |
Elevator.isUp() | 1 | 1 | 1 |
Elevator.judgeDirection() | 6 | 6 | 8 |
Elevator.run() | 4 | 5 | 6 |
Elevator.setAim(int) | 1 | 1 | 1 |
Main.main(String[]) | 3 | 4 | 5 |
Person.Person(PersonRequest) | 1 | 1 | 1 |
Person.getFloorNow() | 1 | 1 | 1 |
Person.getId() | 1 | 1 | 1 |
Person.getTarget() | 1 | 1 | 1 |
Scheduler.Scheduler(ArrayBlockingQueue |
1 | 1 | 1 |
Scheduler.judgeWait() | 1 | 3 | 3 |
Scheduler.run() | 4 | 5 | 5 |
Class | OCavg | WMC | |
Elevator | 3 | 33 | |
Main | 5 | 5 | |
Person | 1 | 4 | |
Scheduler | 2.67 | 8 |
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.Elevator(ArrayBlockingQueue |
1 | 1 | 1 |
Elevator.add() | 1 | 1 | 1 |
Elevator.eleClose() | 1 | 2 | 2 |
Elevator.eleIn() | 4 | 4 | 5 |
Elevator.eleIsEmpty() | 1 | 1 | 1 |
Elevator.eleOpen() | 7 | 7 | 7 |
Elevator.eleOut() | 1 | 4 | 4 |
Elevator.getTotal() | 1 | 1 | 1 |
Elevator.go() | 1 | 2 | 5 |
Elevator.judgeDirection() | 8 | 8 | 12 |
Elevator.run() | 4 | 5 | 6 |
Main.main(String[]) | 3 | 4 | 4 |
Person.Person(PersonRequest) | 1 | 1 | 1 |
Person.getFloorNow() | 1 | 1 | 1 |
Person.getId() | 1 | 1 | 1 |
Person.getTarget() | 1 | 1 | 1 |
Scheduler.Scheduler(ArrayBlockingQueue |
1 | 2 | 2 |
Scheduler.run() | 4 | 8 | 9 |
Scheduler.runElevator() | 1 | 2 | 2 |
Class | OCavg | WMC | |
Elevator | 3.82 | 42 | |
Main | 4 | 4 | |
Person | 1 | 4 | |
Scheduler | 4 | 12 |
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.Elevator(ArrayBlockingQueue |
1 | 1 | 1 |
Elevator.add() | 1 | 1 | 1 |
Elevator.eleClose() | 1 | 2 | 2 |
Elevator.eleIn() | 4 | 4 | 5 |
Elevator.eleIsEmpty() | 1 | 1 | 1 |
Elevator.eleOpen() | 7 | 7 | 7 |
Elevator.eleOut() | 1 | 4 | 4 |
Elevator.getTotal() | 1 | 1 | 1 |
Elevator.go() | 1 | 2 | 5 |
Elevator.judgeDirection() | 8 | 8 | 12 |
Elevator.run() | 4 | 6 | 7 |
Elevator.setInfo(int,int,int,int) | 1 | 1 | 1 |
ElevatorA.ElevatorA(ArrayBlockingQueue |
1 | 1 | 1 |
ElevatorB.ElevatorB(ArrayBlockingQueue |
1 | 1 | 1 |
ElevatorC.ElevatorC(ArrayBlockingQueue |
1 | 1 | 1 |
Main.main(String[]) | 3 | 6 | 6 |
Person.Person(PersonRequest) | 1 | 1 | 1 |
Person.chooseTarget(int) | 2 | 2 | 9 |
Person.directRide(int,int,int) | 1 | 4 | 9 |
Person.getFloorNow() | 1 | 1 | 1 |
Person.getId() | 1 | 1 | 1 |
Person.getTempTarget() | 1 | 1 | 1 |
Person.isA(int) | 1 | 1 | 5 |
Person.isArrive() | 1 | 1 | 1 |
Person.isB(int) | 1 | 1 | 4 |
Person.isC(int) | 1 | 1 | 3 |
Person.leastTask(int,int,int) | 1 | 1 | 6 |
Person.setFloorNow(int) | 1 | 1 | 1 |
Person.setTempTarget(int,int,int) | 2 | 1 | 2 |
Scheduler.Scheduler(ArrayBlockingQueue |
1 | 1 | 1 |
Scheduler.addElevator(ElevatorRequest) | 2 | 2 | 5 |
Scheduler.addRequest(PersonRequest) | 1 | 1 | 1 |
Scheduler.run() | 6 | 8 | 8 |
Scheduler.runElevator() | 1 | 4 | 4 |
Scheduler.wakeElevator() | 1 | 2 | 2 |
Class | OCavg | WMC | |
Elevator | 3.67 | 44 | |
ElevatorA | 1 | 1 | |
ElevatorB | 1 | 1 | |
ElevatorC | 1 | 1 | |
Main | 6 | 6 | |
Person | 2.46 | 32 | |
Scheduler | 3.17 | 19 |
分析自己程序的bug
本单元只有第二次作业中出现了bug(实际上是第一单元的遗留bug),主要是不熟悉多线程程序的运行方式造成的。
由于我的电梯自行判断开门、关门、进人、出人,这四个动作分别使用了四个函数实现,所以就会出现在两个函数执行中间,锁被input抢走的情况。这就会导致,开门判断时,没有可以进入或者离开电梯的乘客,但是input抢走锁输入新的乘客请求后,却会在进人环节非法进入电梯(没有开门)。
仔细想想也是编程的习惯不够好。实际上很显然只有开门之后能考虑进人和出人,而我已经用flag标志了门的位置,即使没想到锁被抢走,也应该加上对于flag的判断,增强程序的鲁棒性。
发现别人bug所采用的策略
主要采用了人工出特殊数据点定点投放+自动化评测相结合的方式。相比起第一单元,终于在三次作业中都实现了全自动化的评测机。不过可能测评机的强度还是不够高,要么是没有跑出别人的bug,要么是跑出的bug无法复现、上交hack也没有成功。
由于这次没有一个hack成功的测试点,可能在发现bug方面采取的策略还不够理想。
心得体会
第二单元我的进步十分明显。一方面通过借鉴学长们的博客,我终于避免了作业迭代过程中的大规模重构;另一方面,我也迭代地完成了三次作业的自动评测机。虽然我的程序在代码分析中仍然有复杂度较高的部分,我的评测机也不够完善,但相比起第一单元的混乱和不知所措已经有了很大的提升。希望下一个单元的作业中我能做的更好。