北航oo第二单元总结

设计策略分析

三次作业都采用生产者消费者的设计模式,电梯调度用了look算法。在第一次作业中,主要有电梯和请求模拟器两个线程。我在第一次作业的设计中加入了调度器,主要负责电梯和请求队列的交互,即在何时的时机从请求队列中取得请求放入调度器的内部队列中,并给电梯发送指令,故电梯运行的逻辑大部分放在调度器中,上、下、开门、关门、上客、下客,都由调度器决定。这样设计的初衷是使电梯线程的逻辑更加简洁,电梯线程只需要从调度器获取指令并照做就行。在第二次作业中,由于有多部电梯,需要分两层调度。第一次作业的调度器事实上维护了电梯内部队列,还需要一个总调度器,用于将请求分配给不同的电梯,故在第二次作业中,加入了总调度器线程,一旦请求队列有请求,就分配给电梯的内部队列。第二次作业对look算法的理解更深入了,所以对第一次作业进行了比较大的重构,将第一次作业的内外队列合并为一个队列,使得单部电梯的逻辑更为简洁,为此,电梯需要与内部队列交互,电梯的逻辑变得更加复杂。所以第二次作业有请求模拟器线程、总调度器线程、和若干电梯线程。对于请求队列来说,请求模拟器线程是生产者,总调度器线程是消费者;对于内部队列来说,总调度器线程是生产者,电梯线程是消费者。第三次作业与第二次作业类似,不同之处在于,对于转乘的请求,首先由总调度器将其拆分为两个请求,然后以乘客为元素发送给电梯的内部队列,当乘客离开电梯时,电梯作为生产者,将下一个请求发送给请求队列。

对于多线程的同步控制,我遇到的难点在于结束所有的线程。对于一组生产者消费者模式的实例,生产者的线程结束时,消费者可能处于请求队列get方法的wait中,无法自动结束。我的处理方法是,在生产者线程即将结束时,调用请求队列中的endProducer方法,这个方法首先将请求队列中的producerEnd属性置为true,然后notifyAll(),用来唤醒正在wait的进程。在get方法的wait的条件中加入&& !producerEnd,一旦生产者的线程结束,便停止wait,返回一个能够识别的返回值,如null。消费者接受到这个特殊的标志后结束消费者的线程。

可扩展性分析

在第三次作业中,我主要关注功能设计,但从最后强测结果来看,性能也还不错。第三次作业中的总调度器主要采取如下策略:

  • 将每一个请求分为可直达请求和转乘请求

  • 可直达请求分配给电梯型号的优先级为C A B,即如果ABC型号的电梯都能满足直达请求,优先分给C型号,然后A型号,最后B型号。这样考虑的理由是C型号停靠的楼层最少,在请求完全随机的情况下,C直达的请求是最少的。

  • 转乘请求最多只转乘一次

  • 转乘请求的转乘楼层需要满足乘客乘坐电梯经过的层数最少,即路程最少

  • 直接将转乘请求拆分为两个请求,然后分配给第一个请求对应的电梯型号

  • 如果存在多个型号相同的电梯,则随机分配给一个电梯

总体来说,没有因为过分追求性能而降低可扩展性,功能设计与性能设计的平衡性较好。事实上,对于look算法,由于输入的不确定性,要想在性能上做出显著的优化是很困难的。我的总调度器并没有对期望的时间进行任何的计算,也没有获得任何电梯的运行信息,只是做了一个简单规则与随机因素结合的分配策略,剩下的留给电梯线程自己去跑。

由于第二次作业进行了比较大的重构,第三次作业的可扩展性已经有比较大的提升。

单一职责原则(Single Responsibility Principle)

这次作业中,我引入了很多的类,将功能细化,职责进行分派。比如将电梯类中的内部队列,轿厢,门,型号信息独立成类,然后再组装成电梯。存在缺陷的是总调度器类,其中有将近一半都在做请求的分类,拆分的事,其实将请求的解析和拆分放在一个独立的类里会更好,这是我在设计时没有考虑周全的地方。

开闭原则(Open Closed Principle)

这是我觉得我欠缺的地方,也是我觉得比较难做到的地方。每次遇到实际问题时,往往会直接上手,缺少抽象的过程,导致代码的灵活性受限,随意我常常遇到某一部分要重构的问题。第三次作业由于不同的类功能分开,一些类的职责较为确定,所以开闭原则比前面的作业要更加满足。

里氏替换原则(Liskov Substitution Principle)

这次作业我设计了电梯类型这个父类,ABC三种电梯类型构成子类,没有重写父类的方法,所有的使用场景都用的子类,所以这条原则得到满足。

迪米特法则(Law of Demeter)

由于采用生产者消费者模式,所以说满足迪米特法则。

接口隔离原则(Interface Segregation Principle)

生产者消费者模式中,类之间只进行必要的交流,没有多余的接口,基本满足接口隔离原则。

依赖倒置原则(Dependence Inversion Principle)

我的理解是依赖倒置原则强调抽象,在抽象间进行交互,在本单元作业的情境下没有具体的体现。

程序结构分析

第三次作业的分析如下

class OCavg WMC
elevator.debug.MyDebug 1.0 3.0
elevator.elevator.AnsweredQueue 3.125 25.0
elevator.elevator.Container 2.0 18.0
elevator.elevator.Direction 2.0 2.0
elevator.elevator.Door 1.0 4.0
elevator.elevator.Elevator 2.1666666666666665 39.0
elevator.elevator.elevatortype.ElevatorType 1.0 11.0
elevator.elevator.elevatortype.ElevatorTypeA 1.0 1.0
elevator.elevator.elevatortype.ElevatorTypeB 1.0 1.0
elevator.elevator.elevatortype.ElevatorTypeC 1.0 1.0
elevator.elevator.Factory 4.0 4.0
elevator.elevator.SubQueue 1.2727272727272727 14.0
elevator.ElevatorPool 2.0833333333333335 25.0
elevator.end.End 1.0 7.0
elevator.Floor 1.2857142857142858 27.0
elevator.MainClass 5.0 5.0
elevator.Passenger 1.2 12.0
elevator.RequestType 1.0 20.0
elevator.Scheduler 5.5 44.0
elevator.UnansweredQueue 1.3333333333333333 8.0
Total   271.0
Average 1.7597402597402598 13.55

 

method ev(G) iv(G) v(G)
elevator.debug.MyDebug.debug() 1.0 1.0 1.0
elevator.debug.MyDebug.Elevator() 1.0 1.0 1.0
elevator.debug.MyDebug.Scheduler() 1.0 1.0 1.0
elevator.elevator.AnsweredQueue.addPassenger(Passenger) 1.0 1.0 1.0
elevator.elevator.AnsweredQueue.AnsweredQueue(String) 1.0 2.0 2.0
elevator.elevator.AnsweredQueue.End() 1.0 1.0 1.0
elevator.elevator.AnsweredQueue.getDirectionWhenIdle(int) 7.0 10.0 12.0
elevator.elevator.AnsweredQueue.getPassengers(int,Direction,int) 1.0 7.0 7.0
elevator.elevator.AnsweredQueue.hasGetInRequest(int,Direction) 1.0 1.0 1.0
elevator.elevator.AnsweredQueue.hasMoreRequestInDirection(int,Direction) 6.0 4.0 6.0
elevator.elevator.AnsweredQueue.isEmpty() 1.0 1.0 1.0
elevator.elevator.Container.addPassenger(Passenger) 1.0 1.0 1.0
elevator.elevator.Container.Container(int) 1.0 1.0 1.0
elevator.elevator.Container.dropPassengers(int) 1.0 3.0 3.0
elevator.elevator.Container.getPassengers() 1.0 1.0 1.0
elevator.elevator.Container.hasDropRequest(int) 3.0 2.0 3.0
elevator.elevator.Container.isEmpty() 1.0 1.0 1.0
elevator.elevator.Container.isFull() 1.0 1.0 1.0
elevator.elevator.Container.margin() 1.0 1.0 1.0
elevator.elevator.Container.noMoreRequestInDirection(int,Direction) 6.0 4.0 6.0
elevator.elevator.Direction.reverse(Direction) 2.0 1.0 2.0
elevator.elevator.Door.Close() 1.0 2.0 2.0
elevator.elevator.Door.Door(int,int) 1.0 1.0 1.0
elevator.elevator.Door.isOpen() 1.0 1.0 1.0
elevator.elevator.Door.Open() 1.0 2.0 2.0
elevator.elevator.Elevator.acceptPassenger(Passenger) 1.0 1.0 1.0
elevator.elevator.Elevator.Close() 1.0 1.0 1.0
elevator.elevator.Elevator.Down() 1.0 3.0 3.0
elevator.elevator.Elevator.dropAll() 1.0 3.0 3.0
elevator.elevator.Elevator.Elevator(String,ElevatorType,UnansweredQueue) 1.0 1.0 1.0
elevator.elevator.Elevator.End() 1.0 1.0 1.0
elevator.elevator.Elevator.getDirection() 1.0 1.0 1.0
elevator.elevator.Elevator.getFloor() 1.0 1.0 1.0
elevator.elevator.Elevator.getLabel() 1.0 1.0 1.0
elevator.elevator.Elevator.getType() 1.0 1.0 1.0
elevator.elevator.Elevator.Idle() 1.0 1.0 1.0
elevator.elevator.Elevator.isFull() 1.0 1.0 1.0
elevator.elevator.Elevator.isIdle() 1.0 2.0 2.0
elevator.elevator.Elevator.loadPerson(Passenger) 1.0 1.0 1.0
elevator.elevator.Elevator.Move() 1.0 2.0 2.0
elevator.elevator.Elevator.Open() 1.0 1.0 1.0
elevator.elevator.Elevator.run() 7.0 15.0 17.0
elevator.elevator.Elevator.Up() 1.0 3.0 3.0
elevator.elevator.elevatortype.ElevatorType.canReach(int) 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.ElevatorType(ArrayList,int,int,String) 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getCapacity() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getCloseTime() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getDefaultFloor() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getFloors() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getLowerFloor(int) 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getMoveTime() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getOpenTime() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getType() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorType.getUpperFloor(int) 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorTypeA.ElevatorTypeA() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorTypeB.ElevatorTypeB() 1.0 1.0 1.0
elevator.elevator.elevatortype.ElevatorTypeC.ElevatorTypeC() 1.0 1.0 1.0
elevator.elevator.Factory.getElevator(String,String,UnansweredQueue) 4.0 4.0 4.0
elevator.elevator.SubQueue.addPassenger(Passenger) 1.0 2.0 2.0
elevator.elevator.SubQueue.hasDirectionRequest(Direction) 2.0 2.0 2.0
elevator.elevator.SubQueue.hasDownPassengers() 1.0 1.0 1.0
elevator.elevator.SubQueue.hasUpPassengers() 1.0 1.0 1.0
elevator.elevator.SubQueue.isDownPassenger(Passenger) 1.0 1.0 1.0
elevator.elevator.SubQueue.isEmpty() 1.0 1.0 1.0
elevator.elevator.SubQueue.isUpPassenger(Passenger) 1.0 1.0 1.0
elevator.elevator.SubQueue.popDirectionPassenger(Direction) 2.0 2.0 2.0
elevator.elevator.SubQueue.popDownPassenger() 1.0 1.0 1.0
elevator.elevator.SubQueue.popUpPassenger() 1.0 1.0 1.0
elevator.elevator.SubQueue.SubQueue(int) 1.0 1.0 1.0
elevator.ElevatorPool.add(Elevator) 1.0 4.0 4.0
elevator.ElevatorPool.areIdle() 7.0 4.0 7.0
elevator.ElevatorPool.ElevatorPool() 1.0 1.0 1.0
elevator.ElevatorPool.getACount() 1.0 1.0 1.0
elevator.ElevatorPool.getBCount() 1.0 1.0 1.0
elevator.ElevatorPool.getCCount() 1.0 1.0 1.0
elevator.ElevatorPool.getElevatorAs() 1.0 1.0 1.0
elevator.ElevatorPool.getElevatorBs() 1.0 1.0 1.0
elevator.ElevatorPool.getElevatorCs() 1.0 1.0 1.0
elevator.ElevatorPool.getElevators() 1.0 1.0 1.0
elevator.ElevatorPool.runAll() 1.0 4.0 4.0
elevator.ElevatorPool.setElevatorsStatus() 1.0 2.0 2.0
elevator.end.End.inputEnd() 1.0 1.0 1.0
elevator.end.End.InputIsEnd() 1.0 1.0 1.0
elevator.end.End.isElevatorsIdle() 1.0 1.0 1.0
elevator.end.End.schedulerEnd() 1.0 1.0 1.0
elevator.end.End.schedulerIsEnd() 1.0 1.0 1.0
elevator.end.End.setElevatorsIdle() 1.0 1.0 1.0
elevator.end.End.setElevatorsNotIdle() 1.0 1.0 1.0
elevator.Floor.abCanReach(int) 1.0 1.0 1.0
elevator.Floor.abcCanReach(int) 1.0 1.0 1.0
elevator.Floor.aCanReach(int) 1.0 1.0 1.0
elevator.Floor.acCanReach(int) 1.0 1.0 1.0
elevator.Floor.bCanReach(int) 1.0 1.0 1.0
elevator.Floor.bcCanReach(int) 1.0 1.0 1.0
elevator.Floor.cCanReach(int) 1.0 1.0 1.0
elevator.Floor.distanceOfFloor(int,int) 2.0 1.0 4.0
elevator.Floor.getFloorCount() 1.0 1.0 1.0
elevator.Floor.getFloorsAB() 1.0 1.0 1.0
elevator.Floor.getFloorsAC() 1.0 1.0 1.0
elevator.Floor.getFloorsBC() 1.0 1.0 1.0
elevator.Floor.getLowerFloor(int) 2.0 1.0 2.0
elevator.Floor.getMaxFloor() 1.0 1.0 1.0
elevator.Floor.getMaxValue() 1.0 1.0 1.0
elevator.Floor.getMinFloor() 1.0 1.0 1.0
elevator.Floor.getMinValue() 1.0 1.0 1.0
elevator.Floor.getUpperFloor(int) 2.0 1.0 2.0
elevator.Floor.isValidFloor(int) 1.0 1.0 3.0
elevator.Floor.keyToValue(int) 2.0 1.0 2.0
elevator.Floor.valueToKey(int) 2.0 1.0 3.0
elevator.MainClass.main(String[]) 3.0 5.0 5.0
elevator.Passenger.equals(Object) 3.0 2.0 4.0
elevator.Passenger.getFromFloor() 1.0 1.0 1.0
elevator.Passenger.getPersonId() 1.0 1.0 1.0
elevator.Passenger.getRequest() 1.0 1.0 1.0
elevator.Passenger.getToFloor() 1.0 1.0 1.0
elevator.Passenger.hashCode() 1.0 1.0 1.0
elevator.Passenger.hasNextRequest() 1.0 1.0 1.0
elevator.Passenger.Passenger(PersonRequest) 1.0 1.0 1.0
elevator.Passenger.Passenger(PersonRequest,PersonRequest) 1.0 1.0 1.0
elevator.Passenger.transfer() 1.0 1.0 1.0
elevator.RequestType.getFromElevator() 1.0 1.0 1.0
elevator.RequestType.getToElevator() 1.0 1.0 1.0
elevator.Scheduler.Dispatch(PersonRequest) 1.0 5.0 5.0
elevator.Scheduler.findBestInterchangeNumber(ArrayList,int,int) 2.0 6.0 6.0
elevator.Scheduler.getInterchangeFloor(String,String,int,int) 4.0 13.0 13.0
elevator.Scheduler.getType(PersonRequest) 13.0 13.0 22.0
elevator.Scheduler.inOrder(int,int,int) 1.0 1.0 4.0
elevator.Scheduler.run() 6.0 7.0 8.0
elevator.Scheduler.Scheduler(UnansweredQueue,ElevatorPool) 1.0 1.0 1.0
elevator.Scheduler.waitForNewRequest() 1.0 1.0 1.0
elevator.UnansweredQueue.addRequest(PersonRequest) 1.0 1.0 1.0
elevator.UnansweredQueue.End() 1.0 1.0 1.0
elevator.UnansweredQueue.getCurrentNum() 1.0 1.0 1.0
elevator.UnansweredQueue.getRequest() 2.0 7.0 8.0
elevator.UnansweredQueue.isEmpty() 1.0 1.0 1.0
elevator.UnansweredQueue.UnansweredQueue(ElevatorPool) 1.0 1.0 1.0
null.getFromElevator() 1.0 1.0 1.0
null.getToElevator() 1.0 1.0 1.0
Total 205.0 253.0 292.0
Average 1.49 1.83 2.16

类图

 

 

优点在于每个类的职责明确,类比较多的情况下,一个类的复杂度能得到控制。

缺点在于类之间的交互比较多,有些类的设置使得类之间有很多间接的交互。

UML时序图

bug分析

第三次作业中互测被发现了两个bug,是由线程不安全导致的。在某种特定的情境下,换乘的人在换乘楼层离开电梯后线程就结束了。错误位于总调度器Scheduler中的run方法的判断线程结束的逻辑中。由于判断Scheduler线程结束需要判断电梯空闲以及请求队列为空,而这两个信息不是同时更新的,会存在线程提前结束的情况。这其实是在设计的时候面临的两难的问题,一方面由电梯作为请求的生产者可以在迭代中减少修改量,另一方面,Scheduler线程结束的判断逻辑会变得很复杂。

没有遇到死锁的情况,经过多次改进也消除了线程结束不了的问题。

bug寻找策略

本单元尝试了编写自动测试脚本进行随机测试,在三次作业中均找到了少量bug,具有一定的有效性,但没能发现自己的bug,说明随机下的数据也不能保证程序的正确性。

由于本单元的作业中的线程间同步和安全问题很难基于规则去针对,于是就采用了随机重复的方法,第一单元具有很多的规则,可以根据规则一一检验,往往很有效。而这一次作业则需要反复多次地测试才可能测出一些bug,在测评机中hack也未必能复现。

心得体会

  • 线程安全不仅仅是共享对象的访问,实际问题可能非常复杂,没有线程的模板,需要作出具体的分析,不断尝试并验证。具有线程思维真的很重要。

  • 对于solid原则,第二单元的设计相比于第一单元要好很多,我认为仍需注意的是加强抽象的意识,这样在面对更庞大,更复杂的问题时会有帮助。

猜你喜欢

转载自www.cnblogs.com/xbr-1111/p/12704191.html
今日推荐