OO第二单元总结
设计策略
类:主要有Dispatcher
主调用器,LiftDispatcher
电梯调度器,LiftState
电梯类,主要是利用方法确定电梯运动、开门、关门等方法,RequestList
等待队伍类,实例化为电梯内的队列和电梯外的队列,完成了进出操作。SafeOutPut
将输出进行安全的封装,可以直接调用进行输出。
共享对象:waitingList
受dispatcher
, addPerson
, liftdispatcher
三个线程访问,是未分配给电梯的等待队列。outEle
受dispatcher
, liftdispatcher
访问,是每个电梯的等待队列(还未进入电梯)
观察者:observer
是观察所有电梯状态的类,dispatcher
通过访问它确定电梯状态后将根据电梯的运行状态和人数判断将人传入电梯的等待队列。
第三次作业的可扩展性
第三次作业耦合性较大,主要是共享对象没有单独存放在一个类中,而是放到了线程的类中。且安全性存在问题,只保证了对象的安全。
SRP
主要将调度器和电梯、队列由不同类进行管理,感觉每个类都有明确的职责,电梯的各个功能通过三个类很好的进行划分。
OCP
基本无法实现。run()
方法较为冗余,方法的封装相对较差,所以继承重写方法时可能会出现很大的问题。
LSP ISP DIP
没有出现父类的继承以及接口和抽象类等。
功能、性能设计
电梯通过AddPerson来添加电梯以及Request,并分配给Dispatcher进行处理。每个Request对应的换乘都是固定的(静态的,不会随电梯状态的变化而变化)。Dispatcher判断将Request分配给对应型号的可以顺带捎带的电梯,并选择这之中等待人数最少的电梯。LiftDispatcher通过scan算法进行人员的送达。经过讨论课同学的讨论,当继续运行方向没有人员时就转换方向。
性能方面从性能分数还可以,第三次作业基本都在97以上。优化可以通过对换乘进行动态的改变。目前是优先电梯A,其次是B,最后是C。如果提前评估运行时间可能难度较大,影响因素很多(等待队列的加入并不会实时反馈给observer)。我认为一个可行的方案是对电梯会暂停的楼层进行标记,通过这个简单计算时间,并进行最优化的解决。但是从作业得分角度来看性价比较低,而且需要增大observer的更新频率,并没有仔细研究。
度量分析
第一次作业
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
AddPerson.AddPerson(Dispatcher) | 1 | 1 | 1 |
AddPerson.run() | 3 | 3 | 4 |
Dispatcher.addTakingRequest(int,RunState) | 1 | 4 | 4 |
Dispatcher.getRequest(Request) | 1 | 2 | 2 |
Dispatcher.setRequest() | 2 | 6 | 6 |
Lift.Lift(int,int,int,Dispatcher) | 1 | 1 | 1 |
Lift.isTakeEmpty() | 1 | 1 | 1 |
Lift.move() | 1 | 14 | 15 |
Lift.offTakeRequest() | 1 | 3 | 3 |
Lift.pickPerson() | 1 | 2 | 2 |
Lift.run() | 3 | 6 | 7 |
Lift.setMainRequest() | 1 | 2 | 2 |
Lift.setTakeRequest() | 1 | 2 | 2 |
Lift.takeToMain() | 1 | 1 | 1 |
LiftState.arrive() | 1 | 1 | 1 |
LiftState.closeDoor() | 1 | 1 | 1 |
LiftState.fromRequest(Request) | 1 | 1 | 1 |
LiftState.getCurFloor() | 1 | 1 | 1 |
LiftState.getDestination() | 1 | 1 | 1 |
LiftState.getRunState() | 1 | 1 | 1 |
LiftState.moveOneFloor() | 3 | 1 | 3 |
LiftState.openDoor() | 1 | 1 | 1 |
LiftState.refresh() | 1 | 1 | 3 |
LiftState.toRequest(Request) | 1 | 1 | 1 |
Main.main(String[]) | 1 | 1 | 1 |
Request.Request(PersonRequest) | 1 | 2 | 2 |
Request.getFromFloor() | 1 | 1 | 1 |
Request.getId() | 1 | 1 | 1 |
Request.getIn() | 1 | 1 | 1 |
Request.getOff() | 1 | 1 | 1 |
Request.getToFloor() | 1 | 1 | 1 |
Request.isIn() | 1 | 1 | 1 |
Request.isNull() | 1 | 1 | 1 |
Request.isUp() | 1 | 1 | 1 |
这里Lift中move方法中没有对队列进行封装,导致队列的操作都在move中完成,且捎带算法中对请求每次都进行便利操作,导致模块设计复杂度和圈复杂度都很高。
第二次作业
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
AddPerson.run() | 3 | 4 | 4 |
Dispatcher.Dispatcher(int) | 1 | 1 | 2 |
Dispatcher.finish() | 1 | 1 | 1 |
Dispatcher.getRequest(MyRequest) | 1 | 1 | 1 |
Dispatcher.run() | 3 | 10 | 10 |
Dispatcher.setRequest(MyRequest) | 1 | 2 | 2 |
LiftDispatcher.LiftDispatcher(int,Observer) | 3 | 2 | 3 |
LiftDispatcher.allEmpty() | 4 | 3 | 5 |
LiftDispatcher.checkInEle(int) | 1 | 1 | 1 |
LiftDispatcher.checkOutEle(int) | 1 | 1 | 1 |
LiftDispatcher.checkPeople() | 4 | 3 | 7 |
LiftDispatcher.finish() | 1 | 1 | 1 |
LiftDispatcher.getInRequest(MyRequest) | 1 | 1 | 1 |
LiftDispatcher.run() | 3 | 16 | 18 |
LiftState.LiftState(int) | 1 | 1 | 1 |
LiftState.closeDoor() | 1 | 1 | 1 |
LiftState.getCurFloor() | 1 | 1 | 1 |
LiftState.getRunState() | 1 | 1 | 1 |
LiftState.moveOneFloor() | 3 | 3 | 5 |
LiftState.openDoor() | 1 | 1 | 1 |
LiftState.refresh() | 1 | 1 | 3 |
LiftState.setDes(int) | 1 | 1 | 1 |
LiftState.setDesExtreme() | 1 | 2 | 2 |
LiftState.setStop() | 1 | 1 | 1 |
Main.main(String[]) | 1 | 1 | 1 |
MyRequest.MyRequest(PersonRequest) | 1 | 2 | 2 |
MyRequest.getFromFloor() | 1 | 1 | 1 |
MyRequest.getId() | 1 | 1 | 1 |
MyRequest.getIn(char) | 1 | 1 | 1 |
MyRequest.getOff(char) | 1 | 1 | 1 |
MyRequest.getToFloor() | 1 | 1 | 1 |
MyRequest.isNull() | 1 | 1 | 1 |
Observer.Observer(int) | 1 | 1 | 2 |
Observer.canGetIn(int) | 3 | 5 | 9 |
Observer.update(int,int,RunState,int) | 1 | 1 | 1 |
RequestList.InLift(HashMap |
3 | 2 | 3 |
RequestList.OutLift() | 1 | 2 | 2 |
RequestList.RequestList(int) | 1 | 1 | 1 |
RequestList.addRequest(MyRequest) | 1 | 1 | 1 |
RequestList.isEmpty() | 1 | 1 | 1 |
相对还是两个调度器中run()
方法很多封装不够。比如电梯开关门的空隙应该是交给LiftState
来进行管理等等。由于hw6是重构的,相对还是比hw7和hw5要很多。
第三次作业
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
AddPerson.run() | 3 | 7 | 7 |
Dispatcher.Dispatcher() | 1 | 1 | 1 |
Dispatcher.addChange(AbstractList ) | 1 | 1 | 1 |
Dispatcher.addLift(ElevatorRequest) | 1 | 1 | 1 |
Dispatcher.finish() | 1 | 1 | 1 |
Dispatcher.getRequest(MyRequest) | 1 | 1 | 1 |
Dispatcher.run() | 3 | 11 | 15 |
Dispatcher.setRequest(MyRequest) | 1 | 2 | 2 |
Dispatcher.setStart(int,boolean) | 1 | 1 | 1 |
LiftDispatcher.LiftDispatcher(int,Observer,String,String,Dispatcher) | 3 | 3 | 5 |
LiftDispatcher.allEmpty() | 4 | 3 | 5 |
LiftDispatcher.checkInEle(int) | 1 | 1 | 1 |
LiftDispatcher.checkOutEle(int) | 1 | 1 | 1 |
LiftDispatcher.checkPeople() | 4 | 3 | 7 |
LiftDispatcher.finish() | 1 | 1 | 1 |
LiftDispatcher.getInRequest(MyRequest) | 1 | 1 | 1 |
LiftDispatcher.run() | 3 | 16 | 18 |
LiftState.LiftState(int,String) | 1 | 1 | 1 |
LiftState.closeDoor() | 1 | 1 | 1 |
LiftState.getCurFloor() | 1 | 1 | 1 |
LiftState.getRunState() | 1 | 1 | 1 |
LiftState.moveOneFloor() | 3 | 3 | 5 |
LiftState.openDoor() | 1 | 1 | 1 |
LiftState.refresh() | 1 | 1 | 3 |
LiftState.setDes(int) | 1 | 1 | 1 |
LiftState.setDesExtreme() | 1 | 2 | 2 |
LiftState.setStop() | 1 | 1 | 1 |
Main.main(String[]) | 1 | 1 | 1 |
MyRequest.MyRequest(PersonRequest) | 1 | 1 | 2 |
MyRequest.find(int[],int) | 3 | 1 | 3 |
MyRequest.getEleA() | 1 | 1 | 1 |
MyRequest.getEleB() | 1 | 1 | 1 |
MyRequest.getEleC() | 1 | 1 | 1 |
MyRequest.getFromFloor() | 1 | 1 | 1 |
MyRequest.getId() | 1 | 1 | 1 |
MyRequest.getIn(String) | 1 | 1 | 1 |
MyRequest.getOff(String) | 1 | 1 | 1 |
MyRequest.getToFloor() | 1 | 1 | 1 |
MyRequest.isNull() | 1 | 1 | 1 |
MyRequest.isUp() | 1 | 1 | 1 |
MyRequest.refresh() | 2 | 1 | 2 |
MyRequest.setRequest(boolean) | 1 | 4 | 54 |
Observer.Observer() | 1 | 1 | 2 |
Observer.addEle(int,String) | 1 | 1 | 1 |
Observer.allStop() | 3 | 2 | 3 |
Observer.canGetIn(MyRequest) | 7 | 7 | 10 |
Observer.getIn(int,String) | 4 | 5 | 10 |
Observer.update(int,int,RunState,int) | 1 | 1 | 1 |
RequestList.InLift(HashMap |
3 | 2 | 3 |
RequestList.OutLift(AbstractList ) | 1 | 3 | 3 |
RequestList.RequestList(int,String) | 1 | 1 | 1 |
RequestList.addRequest(MyRequest) | 1 | 1 | 1 |
RequestList.isEmpty() | 1 | 1 | 1 |
SafeOutPut.safePrint(String) | 1 | 1 | 1 |
和hw6一样,还是两个调度器的run()
方法有一些复杂。这里的setRequest()
方法由于是打表来完成,导致圈复杂度极高。应该用一个类专门去计算如何去换乘,而不是用打表方法可能会更好。另外observer
中,canGetIn()
和getIn()
的模块复杂度比较高,是由于对每个电梯的状态进行判断通过遍历的方式来找到最好的电梯。目前没有更好地解决办法。
分析自己程序的bug
bug主要分为三类,线程相关的bug,逻辑上的bug,玄学bug
线程相关bug主要是只注重了对共享对象的保护,但是没有考虑访问共享对象时的操作的整体性(原子性),实际上这里并没有被hack到,是自己进行完实验后对同步有更深刻的理解后,debug的时候发现的。
逻辑上的bug主要是没有对逻辑进行分析。过分依靠自己不靠谱的评测机,导致没有仔细考虑逻辑而只关注了线程。比如电梯人满是先加人再判断还是先判断再加人;即使有人在20/-3层但是接不到,就不考虑换乘。这些基本是阅读代码的时候发现的。
玄学bug一共遇到了两个,一个是莫名其妙停止一段时间后又继续运行。没有找到原因。同学说是同步块中出现了睡眠,但是一直没有复现过,还在寻找原因。由于时间间隔越来越长,差不多是两倍关系,我猜测是不是和request被循环加入了?目前还在寻找。
另外一个是换乘后的请求被吃掉了,但是所有线程还是正常退出了。由于本地刚开始没有复现成功所以进行了分析。最后猜测是由于输入是同一时间,所以null给到dispatcher的时候电梯还没有运行(刚收到request),所以dispatcher误以为所有电梯都停止了可以结束了。之后又修改了测评机之后发现的确是这个原因。因为自己输入样例的时候crtl+D不和请求一起输入,所以没有复现。
发现别人的bug
第一单元由于测评机比较靠谱,所以第二单元还是主要依靠测评机+努力看懂同学代码。但是测评机书写有问题,自己程序的bug都没有发现。且没有仔细考虑边缘数据。
测评机主要问题是无法判断ctle和rtle,且电脑内存太小动不动就卡也不知道是因为电脑卡还是真的没有输出。应该对测评机有强制结束的环节。且测评机没有针对极端数据,这点应该以后会有改善。(可能极端数据还是手搓更好)
心得体会
目前对迭代的感觉还不是很深。主要如果一次迭代代码改完还可以,但是迭代完两次后代码就很难读懂了,且框架就极度恶心。主要问题是在迭代过程中主要是对类进行方法、属性扩增,而很少考虑对类进行扩增。以后每次迭代应该都不先考虑如何实现目的,而是先考虑框架上的变化。
测评机坑爹,过分依赖不靠谱的测评机很不友好。且本地测试和服务器测试差别较大,有的时候测评机无法复线心态就不太好了。测评机的书写也比较乱,导致hw5用完更新到hw6就很费劲。以后写测评机也应该考虑一下功能的扩展。并且看同学的测评机,认为测评机还可以从以下几点进行优化:对错误有更好的报告;开更多的线程来进行测试;通过测评机对性能进行一定的测试。
对线程安全的理解可能是经历了第二次试验后才感觉真正明白,导致hw5、hw6、hw7的线程安全很是问题。且没有大胆使用lock还是有点遗憾。由于线程安全一直没有进行修改,所以保证线程安全一直有线程来保护,而不是共享对象保护,导致逻辑比较复杂。在hw7的debug中进行了一定更改,但是还是比较复杂。