面向对象第二单元总结——魔鬼电梯

写在前面

OO课程已经过半,过去的第二单元主要是训练了我们的多线程设计,以电梯为载体,步步深入,层层递进。本单元我学到了:

  • 如何做一个线程安全的设计。
  • 如何去合理地使用Java的锁机制。

下面我将以三次作业为例,具体谈一下我的收获。

第五次作业

这次作业的要求是写一个傻瓜式调度的电梯,笔者也是按指导书去写的,整体写起来很轻松,也算入门了Java多线程的写法、共享对象的使用以及锁的使用。

代码规模

图片名称

类图

图片名称

UML

图片名称

第一次的业务逻辑很清晰,就是简单的生产者-消费者模型,输入类为生产者,电梯类为消费者,共享对象是调度器里的请求队列。可以很简单地实现傻瓜电梯的功能,也不容易出bug。

复杂度分析

Method ev(G) iv(G) v(G)
elevator.Elevator.Elevator(Scheduler) 1 1 1
elevator.Elevator.moveToFloor(int) 1 2 2
elevator.Elevator.personIn(int) 1 2 2
elevator.Elevator.personOut(int) 1 2 2
elevator.Elevator.run() 3 3 3
elevator.Main.main(String[]) 1 1 1
elevator.RequestIn.RequestIn(Scheduler) 1 1 1
elevator.RequestIn.run() 3 4 4
elevator.Scheduler.Scheduler() 1 1 1
elevator.Scheduler.addRequest(PersonRequest) 1 1 1
elevator.Scheduler.getQueue() 1 1 3
elevator.Scheduler.getRequest() 1 3 3
Class OCavg WMC
elevator.Elevator 1.4 7
elevator.Main 1 1
elevator.RequestIn 2 4
elevator.Scheduler 1.75 7

可以看到第一次业务逻辑简单,代码的复杂度也相对小一点,只有电梯的运行方法稍显复杂,这也是情理之中的。

SOLID原则分析

本单元作业笔者未使用继承和接口,所以以下只分析SRP和OCP。

单一责任原则(SRP)

笔者的设计符合SRP原则,输入类只负责输入,电梯只负责运行,调度器只负责存储请求队列。

开放封闭原则(OCP)

笔者在第一次作业中暂未考虑扩展性问题。

Bug分析

公测

笔者的程序在公测中未被发现bug。

互测

笔者的程序在互测中未被发现bug。

测试方法

随机生成测试数据,用java程序按时间点向电梯输入请求,得到输出后用python程序检查最终的电梯状态和人员状态,看一下调度是否正确。

第六次作业

本次作业的要求是写一个ALS调度算法的电梯,由于这次作业加入了性能分评测,故笔者写的电梯是Look调度算法。这次作业的逻辑稍显复杂,怎么去避免死锁发生,怎么去写一个线程安全的架构是我们考虑的重点。

代码规模

图片名称

类图

图片名称

UML

图片名称

第二次作业相比第一次作业,只增加了捎带需求,而为了适应多电梯的扩展,我对电梯系统做了重构,首先,增加Elevatorbuild类,负责生成多部电梯,然后Elevator按Look调度算法运行,每到一层楼都和Scheduler调度器进行一次交互,进行接送乘客。因为是单电梯,所以本次的调度器职责就是把收到的指令分配给电梯。

复杂度分析

Method ev(G) iv(G) v(G)
Main.main(String[]) 1 1 1
elevatorsystem.RequestInput.RequestInput(Scheduler) 1 1 1
elevatorsystem.RequestInput.run() 3 4 4
elevatorsystem.Scheduler.addRequest(PersonRequest) 1 1 1
elevatorsystem.Scheduler.getScheduler() 1 1 3
elevatorsystem.Scheduler.setRunning(Boolean) 1 1 1
elevatorsystem.elevator.Elevator.Elevator() 1 1 1
elevatorsystem.elevator.Elevator.check() 1 6 8
elevatorsystem.elevator.Elevator.close() 1 2 2
elevatorsystem.elevator.Elevator.doWait() 1 2 2
elevatorsystem.elevator.Elevator.elevatorStop() 1 6 11
elevatorsystem.elevator.Elevator.getArrival() 1 1 1
elevatorsystem.elevator.Elevator.getDirection() 1 1 1
elevatorsystem.elevator.Elevator.getFloor() 1 1 1
elevatorsystem.elevator.Elevator.getNextRequest() 1 4 4
elevatorsystem.elevator.Elevator.getRequestNum() 1 1 1
elevatorsystem.elevator.Elevator.getRunning() 1 1 1
elevatorsystem.elevator.Elevator.getStatus() 1 1 1
elevatorsystem.elevator.Elevator.isDown(PersonRequest) 1 1 1
elevatorsystem.elevator.Elevator.isUp(PersonRequest) 1 1 1
elevatorsystem.elevator.Elevator.load() 1 6 6
elevatorsystem.elevator.Elevator.lookDown() 4 2 4
elevatorsystem.elevator.Elevator.lookUp() 4 2 4
elevatorsystem.elevator.Elevator.moveDown() 1 2 3
elevatorsystem.elevator.Elevator.moveUp() 1 2 3
elevatorsystem.elevator.Elevator.open() 1 2 2
elevatorsystem.elevator.Elevator.run() 6 16 17
elevatorsystem.elevator.Elevator.setArrival(ArrayList) 1 1 1
elevatorsystem.elevator.Elevator.setDirection(int) 1 1 1
elevatorsystem.elevator.Elevator.setFloor(int) 1 1 1
elevatorsystem.elevator.Elevator.setInf(int) 1 1 1
elevatorsystem.elevator.Elevator.setRequestNum(Integer) 1 1 1
elevatorsystem.elevator.Elevator.setRunning(Boolean) 1 1 1
elevatorsystem.elevator.Elevator.setStatus(Integer,ArrayList) 1 1 1
elevatorsystem.elevator.Elevator.setSup(int) 1 1 1
elevatorsystem.elevator.Elevator.unload() 1 3 3
elevatorsystem.elevator.ElevatorBuild.InitOne() 3 2 3
elevatorsystem.elevator.ElevatorBuild.getElevatorBuild() 1 1 3
elevatorsystem.elevator.ElevatorBuild.getElevatorOne() 1 1 1
elevatorsystem.elevator.ElevatorBuild.run() 1 1 1
Class OCavg WMC
Main 1 1
elevatorsystem.RequestInput 2 4
elevatorsystem.Scheduler 1.67 5
elevatorsystem.elevator.Elevator 2.17 65
elevatorsystem.elevator.ElevatorBuild 2 8

不出所料,仍然是Elevator的运行方法和每层楼的接送乘客方法复杂度较高。

SOLID原则分析

单一责任原则(SRP)

笔者的设计符合SRP原则,输入类只负责输入,电梯的Builder类只负责生成电梯,电梯只负责运行已收到的请求,调度器只负责将收到的请求分给电梯。

开放封闭原则(OCP)

笔者在这次作业中充分考虑了向多电梯的可扩展性,新增ElevatorBuild类,负责根据不同参数生成不同种类的电梯,所以这个架构在第三次作业中得以沿用。

Bug分析

公测

笔者的程序在公测中未被发现bug。

互测

笔者的程序在互测中未被发现bug。笔者发现其他同学的一个bug,在某种情况下,他的电梯会一直向上或向下运行而不会停下(所谓的飞天遁地),原因应该是对边界楼层和电梯转向的处理不严密。

测试方法

笔者沿用了第一次作业的测试工具,定时投放+输出数据正确性检测。

第七次作业

最后一次作业增加了多电梯(3个),并且每个电梯的可到达楼层不同,需要考虑换乘的情况,整体难度较大,并且对线程稳定性的要求也很高。笔者的设计是采用三台Look调度算法的电梯,每个电梯只负责自己请求队列里的指令,需要换乘的指令会在指令输入的时候被拆分成两条指令,在第一条指令执行完毕后将第二条指令加入请求队列。

代码规模

图片名称

类图

图片名称

UML

图片名称

可以看到,对于笔者的架构,从第二次作业到第三次作业,几乎只需要修改调度器,其他部分不做改动,所以笔者这次作业完成地也比较轻松。

复杂度分析

Method ev(G) iv(G) v(G)
Main.main(String[]) 1 1 1
elevatorsystem.RequestInput.RequestInput(Scheduler) 1 1 1
elevatorsystem.RequestInput.run() 3 4 4
elevatorsystem.Scheduler.InitChange(ArrayList<ArrayList>) 1 2 2
elevatorsystem.Scheduler.Scheduler() 1 1 1
elevatorsystem.Scheduler.addRequest(PersonRequest) 1 10 10
elevatorsystem.Scheduler.checkStatus(char,ArrayList) 6 9 9
elevatorsystem.Scheduler.createMapA() 1 5 10
elevatorsystem.Scheduler.createMapB() 1 5 8
elevatorsystem.Scheduler.createMapC() 1 5 6
elevatorsystem.Scheduler.doWait(Object) 1 2 2
elevatorsystem.Scheduler.getChange() 1 1 1
elevatorsystem.Scheduler.getLock() 1 1 1
elevatorsystem.Scheduler.getMap(char) 3 1 3
elevatorsystem.Scheduler.getQueue(char) 3 1 3
elevatorsystem.Scheduler.getRequestNum(char) 3 1 3
elevatorsystem.Scheduler.getRunning() 1 1 1
elevatorsystem.Scheduler.getScheduler() 1 1 3
elevatorsystem.Scheduler.lock() 1 1 1
elevatorsystem.Scheduler.mapInit(HashMap<Integer, ArrayList>) 3 2 3
elevatorsystem.Scheduler.removeRequest(PersonRequest) 1 1 4
elevatorsystem.Scheduler.requestMapInit(HashMap<Integer, ArrayList>) 3 2 3
elevatorsystem.Scheduler.setRunning(Boolean) 1 1 1
elevatorsystem.Scheduler.takeApart(PersonRequest) 1 10 10
elevatorsystem.Scheduler.unLock() 1 1 1
elevatorsystem.elevator.Elevator.Elevator(Scheduler,Object) 1 1 1
elevatorsystem.elevator.Elevator.addAvailableFloor(int) 1 1 1
elevatorsystem.elevator.Elevator.check() 1 7 9
elevatorsystem.elevator.Elevator.close() 1 2 2
elevatorsystem.elevator.Elevator.doWait(Object) 1 2 2
elevatorsystem.elevator.Elevator.elevatorStop() 1 5 10
elevatorsystem.elevator.Elevator.getArrival() 1 1 1
elevatorsystem.elevator.Elevator.getDirection() 1 1 1
elevatorsystem.elevator.Elevator.getFloor() 1 1 1
elevatorsystem.elevator.Elevator.getNextRequest() 1 4 4
elevatorsystem.elevator.Elevator.isDown(PersonRequest) 1 1 1
elevatorsystem.elevator.Elevator.isUp(PersonRequest) 1 1 1
elevatorsystem.elevator.Elevator.load() 3 6 7
elevatorsystem.elevator.Elevator.lookDown() 4 2 4
elevatorsystem.elevator.Elevator.lookUp() 4 2 4
elevatorsystem.elevator.Elevator.moveDown() 1 2 3
elevatorsystem.elevator.Elevator.moveUp() 1 2 3
elevatorsystem.elevator.Elevator.open() 1 2 2
elevatorsystem.elevator.Elevator.run() 6 17 19
elevatorsystem.elevator.Elevator.setArrival(ArrayList) 1 1 1
elevatorsystem.elevator.Elevator.setDirection(int) 1 1 1
elevatorsystem.elevator.Elevator.setFloor(int) 1 1 1
elevatorsystem.elevator.Elevator.setInf(int) 1 1 1
elevatorsystem.elevator.Elevator.setMaxContain(int) 1 1 1
elevatorsystem.elevator.Elevator.setMoveTime(int) 1 1 1
elevatorsystem.elevator.Elevator.setName(char) 1 1 1
elevatorsystem.elevator.Elevator.setOpenTime(int) 1 1 1
elevatorsystem.elevator.Elevator.setSup(int) 1 1 1
elevatorsystem.elevator.Elevator.unload() 1 7 8
elevatorsystem.elevator.ElevatorBuild.ElevatorBuild(Scheduler) 1 1 1
elevatorsystem.elevator.ElevatorBuild.InitA() 1 3 5
elevatorsystem.elevator.ElevatorBuild.InitB() 1 3 4
elevatorsystem.elevator.ElevatorBuild.InitC() 1 3 3
elevatorsystem.elevator.ElevatorBuild.getElevatorA() 1 1 1
elevatorsystem.elevator.ElevatorBuild.getElevatorB() 1 1 1
elevatorsystem.elevator.ElevatorBuild.getElevatorBuild(Scheduler) 1 1 3
elevatorsystem.elevator.ElevatorBuild.getElevatorC() 1 1 1
elevatorsystem.elevator.ElevatorBuild.run() 1 1 1
Class OCavg WMC
Main 1 1
elevatorsystem.RequestInput 2 4
elevatorsystem.Scheduler 3.14 69
elevatorsystem.elevator.Elevator 2.45 71
elevatorsystem.elevator.ElevatorBuild 1.89 17

可以看到,调度器的添加请求方法和拆分请求方法,以及电梯的run方法复杂度很高。结合代码不难看出复杂度高的原因。调度器的添加请求方法需要做这么几件事:

  • 判断电梯是否可直达,是否满载,如果未满载并且可直达则加入请求;
  • 如果全部满载则选择一部可直达的电梯加入请求;
  • 如果不可直达,则调用拆分请求方法进行指令拆分和添加。

这其中有很复杂的条件语句,所以方法复杂度略高。

至于电梯的run方法,是负责整个电梯运行逻辑的,所以复杂度略高也合乎情理。

SOLID原则分析

单一责任原则(SRP)

笔者的设计符合SRP原则,输入类只负责输入,电梯的Builder类只负责生成电梯,电梯只负责运行已收到的请求,调度器只负责接收和拆分请求,并分配给不同的电梯。

开放封闭原则(OCP)

笔者在这次作业中保留了对更多电梯的可扩展性,通过ElevatorBuild类可进行扩展。但是本次作业我的调度器是每个电梯一个请求队列,所以扩展起来比较麻烦,我想如果设计成所有电梯共用一个请求队列的话,应该会更加便于扩展。

Bug分析

公测

本次公测笔者的程序炸了很多点,原因是有一个条件语句加错了位置,更改后可稳定通过全部测试点。

互测

笔者找到其他同学很多bug,有程序无法结束的,有电梯超载的,有运行时间过长的,还有乘客未送到指定位置的。毕竟是C组,这么多bug也就不足为奇了。

总结

多线程设计让我明白了以下几点:

  • 一定要充分思考以后再动手写代码,不然在多线程设计中出了bug是很难调试的。代码不规范,debug两行泪。
  • 不要盲目地去使用锁,synchronized固然好用,但是也不能滥用,要明确锁的是哪个对象,哪个类。synchronized使用也是有技巧的,有时候一个方法里面只需要锁住一个对象就可以,没必要把整个方法都锁起来,这样会造成效率的降低。
  • 每写好一个版本后,都需要对应充分的测试,一个优秀的工程设计师是兼顾正确性和高性能的,不要为了追求一点点的性能而在设计上产生漏洞,更不要目测程序没问题就不去做充分的测试。这是我在第三次作业中学到的,没测试充分,导致bug没被发现,所以炸掉了强测。

希望在以后的学习中,我可以更好地掌握测试方法,写出更加完美的程序。

posted @ 2019-04-23 11:32  mc_steven  阅读(439)  评论(0编辑  收藏  举报