2019年面向对象第二单元总结
一、第一次作业
第一次作业需要完成的任务为单部多线程傻瓜调度(FAFS)电梯的模拟,指的是按照请求进入系统的顺序,依次按顺序逐个执行运送任务,
本次作业无需考虑实际电梯运行中顺路捎带等情况。
类图
调度策略
本次作业符合课上讲述的生产者—消费者模式,这里的生产者是乘客的乘坐电梯的请求,消费者是电梯。生产者生成请求放入托盘中,把托盘作为生产者和消费者的共享对象,电梯按照乘客的先后顺序,从托盘中取出请求并进行处理。我采用的是指导书给出的架构:主线程进行输入的管理,使用RequestInput,负责接收请求并存入RequestQueue类中的queue;开一个Dispatch线程,用于模拟电梯的活动,负责从队列中取出请求并进行执行,执行完后继续取继续执行,结束线程的条件是主线程输入为null并且从queue中取出的请求为null;RequestQueue类中的queue,使用的是阻塞队列,RequestQueue提供的方法有插入请求、取出请求和发出结束信号,使用wait和notify保证队列是线程安全的。
Bug分析
由于本次的代码实现较为简单,只需保证共享对象RequestQueue的安全访问和程序的正常结束,其它方面没有太大的问题。我的程序在强测和互测中没有发现问题,在互测中也没有发现其他同学的bug。在找其他同学的bug时,我主要验证结束条件的正确性还有请求队列的操作是否保证线程安全。
代码度量
Complexity Metrics(复杂度分析)
我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径数量得来。
- ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在$[1,v(G)]$之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
- iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在$[1,v(G)]$之间,值越大联系越紧密。
- v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
Complexity metrics | 星期一 | 22 四月 2019 23:17:34 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
elevator1.Dispatch.Dispatch() | 1 | 1 | 1 |
elevator1.Dispatch.Dispatch(RequestQueue) | 1 | 1 | 1 |
elevator1.Dispatch.abs(int) | 2 | 1 | 2 |
elevator1.Dispatch.run() | 3 | 7 | 8 |
elevator1.Main.main(String[]) | 1 | 1 | 1 |
elevator1.PersonDemand.PersonDemand() | 1 | 1 | 1 |
elevator1.PersonDemand.PersonDemand(int,int,int) | 1 | 1 | 1 |
elevator1.PersonDemand.getFromFloor() | 1 | 1 | 1 |
elevator1.PersonDemand.getPersonId() | 1 | 1 | 1 |
elevator1.PersonDemand.getToFloor() | 1 | 1 | 1 |
elevator1.RequestInput.RequestInput(RequestQueue) | 1 | 1 | 1 |
elevator1.RequestInput.run() | 3 | 4 | 4 |
elevator1.RequestQueue.RequestQueue() | 1 | 1 | 1 |
elevator1.RequestQueue.addrequest(PersonRequest) | 1 | 1 | 2 |
elevator1.RequestQueue.fetchrequest() | 1 | 6 | 6 |
elevator1.RequestQueue.setend(boolean) | 1 | 1 | 1 |
Class | OCavg | WMC | |
elevator1.Dispatch | 2.75 | 11 | |
elevator1.Main | 1 | 1 | |
elevator1.PersonDemand | 1 | 5 | |
elevator1.RequestInput | 2 | 4 | |
elevator1.RequestQueue | 1.5 | 6 | |
Package | v(G)avg | v(G)tot | |
elevator1 | 2.06 | 33 | |
Module | v(G)avg | v(G)tot | |
homework_5 | 2.06 | 33 | |
Project | v(G)avg | v(G)tot | |
project | 2.06 | 33 |
因为本次作业架构比较简单,所以不论类复杂度还是方法复杂度都比较低。
Dependency Metrics(依赖度分析)
依赖度分析度量了类之间的依赖程度。有如下几种项目:
- Cyclic:指和类直接或间接相互依赖的类的数量。这样的相互依赖可能导致代码难以理解和测试。
- Dcy和Dcy:计算了该类直接依赖的类的数量,带*表示包括了间接依赖的类。
- Dpt和Dpt:计算了直接依赖该类的类的数量,带*表示包括了间接依赖的类。
Dependency metrics | 星期一 | 22 四月 2019 23:20:40 CST | |||
---|---|---|---|---|---|
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* |
elevator1.Dispatch | 0 | 2 | 2 | 1 | 1 |
elevator1.Main | 0 | 3 | 4 | 0 | 0 |
elevator1.PersonDemand | 0 | 0 | 0 | 2 | 4 |
elevator1.RequestInput | 0 | 1 | 2 | 1 | 1 |
elevator1.RequestQueue | 0 | 1 | 1 | 3 | 3 |
Package | PDcy | PDpt | |||
elevator1 | 0 | 0 |
UML协作图
设计原则检查
- SRP(单一责任原则):避免类的功能重合和一个类做太多事。类和方法的功能行为单一,比较符合。
- OCP(开放封闭原则):对扩展开放,对修改封闭。第一次作业自然符合该原则。
- LSP(里氏替换原则):子类应该包括父类的所有属性。在设计中没有出现继承。
- ISP(接口分离原则):避免接口的责任重合和一个接口做太多事情。未采用接口设计。
- DIP(依赖倒置原则):模块之间尽可能依赖于抽象实现,而不是模块之间的依赖,抽象不能依赖于细节。这个作业没有能够有效降低模块之间的耦合度。
二、第二次作业
第二次作业要求完成单部多线程可捎带调度(ALS)电梯的模拟。和第一次作业一样,仍然是一部电梯,不同的是调度策略的改变和运行楼层的改变(加入了负数层)。ALS策略即在电梯的运行过程中,如果在某一楼层有乘客的目标方向与电梯当前运动方向相同,则可以把符合条件的乘客载入电梯中,并执行请求。
类图
调度策略
这次作业可以在第一次作业的基础上进行功能的扩展,输入进程不需要改变,做出的扩展如下:在RequestQueue中新建了pickupqueue变量,用于存放捎带请求;新增PersonIn和PersonOut方法,实现当前楼层的上下人操作,上人时把queue中符合要求的乘客从队列中移出,放入pickupqueue中,下人时把pickupqueue中符合要求的乘客从队列中移出。同样仍然需要保证queue和pickupqueue是线程安全的,因为只有一个电梯,所以pickupqueue的插入和删除是有先后顺序的;queue的访问控制通过wait和notify实现,RequestQueue的方法使用synchronized机制。程序正常退出的条件是RequestInput输入为null并且pickupqueue中的乘客请求全部处理完毕并且从queue取出的请求为null。
Bug分析
在第一次中测时,测试点全部超时,原因是程序在退出的时候没有退出,当我修改了程序退出条件后,仍有测试点没有通过,原因是如果一层有许多从1-15层的乘客,按照我的设计电梯只能载一个乘客,而其他乘客需要等待电梯把第一位乘客送到15层然后返回一层,这个设计很容易导致程序超时。当我增加判断条件后,终于通过测试点。在强测中和互测中,我的程序没有发现bug,在互测中我也没有发现其它同学的bug。在设计中,我在实现基本功能后没有进行性能的优化,导致强测中的性能分非常低。
代码度量
Complexity Metrics(复杂度分析)
Complexity metrics | 星期一 | 22 四月 2019 23:19:11 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
elevator2.Dispatch.Dispatch() | 1 | 1 | 1 |
elevator2.Dispatch.Dispatch(RequestQueue) | 1 | 1 | 1 |
elevator2.Dispatch.abs(int) | 2 | 1 | 2 |
elevator2.Dispatch.run() | 5 | 9 | 11 |
elevator2.Dispatch.updatefloorduring() | 1 | 6 | 7 |
elevator2.Dispatch.updatefloorfrom() | 1 | 6 | 7 |
elevator2.Main.main(String[]) | 1 | 1 | 1 |
elevator2.PersonDemand.PersonDemand() | 1 | 1 | 1 |
elevator2.PersonDemand.PersonDemand(int,int,int) | 1 | 1 | 2 |
elevator2.PersonDemand.getFromFloor() | 1 | 1 | 1 |
elevator2.PersonDemand.getPersonId() | 1 | 1 | 1 |
elevator2.PersonDemand.getToFloor() | 1 | 1 | 1 |
elevator2.PersonDemand.getupdown() | 1 | 1 | 1 |
elevator2.RequestInput.RequestInput(RequestQueue) | 1 | 1 | 1 |
elevator2.RequestInput.run() | 3 | 4 | 4 |
elevator2.RequestQueue.PersonIn(int,int) | 1 | 6 | 6 |
elevator2.RequestQueue.PersonOut(int,int) | 1 | 5 | 5 |
elevator2.RequestQueue.RequestQueue() | 1 | 1 | 1 |
elevator2.RequestQueue.addrequest(PersonRequest) | 1 | 2 | 3 |
elevator2.RequestQueue.fetchrequest() | 1 | 6 | 6 |
elevator2.RequestQueue.getpickupqueue() | 1 | 1 | 1 |
elevator2.RequestQueue.setend(boolean) | 1 | 1 | 1 |
Class | OCavg | WMC | |
elevator2.Dispatch | 3.5 | 21 | |
elevator2.Main | 1 | 1 | |
elevator2.PersonDemand | 1.17 | 7 | |
elevator2.RequestInput | 2 | 4 | |
elevator2.RequestQueue | 2.14 | 15 | |
Package | v(G)avg | v(G)tot | |
elevator2 | 2.95 | 65 | |
Module | v(G)avg | v(G)tot | |
homework_6 | 2.95 | 65 | |
Project | v(G)avg | v(G)tot | |
project | 2.95 | 65 |
本次设计方法复杂度和类复杂度控制得还可以,RequestQueue新增了属性和方法,较第一次增加了不少,Dispatch的run方法进行了改进,复杂度也有所增加。
Dependency Metrics(依赖度分析)
Dependency metrics | 星期一 | 22 四月 2019 23:21:20 CST | |||
---|---|---|---|---|---|
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* |
elevator2.Dispatch | 0 | 2 | 2 | 1 | 1 |
elevator2.Main | 0 | 3 | 4 | 0 | 0 |
elevator2.PersonDemand | 0 | 0 | 0 | 2 | 4 |
elevator2.RequestInput | 0 | 1 | 2 | 1 | 1 |
elevator2.RequestQueue | 0 | 1 | 1 | 3 | 3 |
Package | PDcy | PDpt | |||
elevator2 | 0 | 0 |
UML协作图
设计原则检查
- SRP(单一责任原则):类/方法的功能行为单一,基本符合该原则。
- OCP(开放封闭原则):在第一次作业基础上进行拓展,符合开闭原则。
- LSP(里氏替换原则):没有使用继承。
- ISP(接口分离原则):没有采用接口设计。
- DIP(依赖倒置原则):模块之间的耦合度有点高,没有充分进行抽象实现。
三、第三次作业
第三次作业要求实现的是多部多线程智能(SS)调度电梯的模拟。本次作业与第二次作业相比,有以下不同:从一部电梯过渡到三部电梯;电梯的运行楼层进行了扩展;三部电梯的可停靠楼层、运行时间、最大载客量都不相同。
类图
调度策略
第二次作业设计比较难过渡到三部电梯,因此需要进行重构。本次作业我采用的课上介绍的Worker Thread模式,ClientThread进行输入的管理,使用ElevatorInput,负责接收请求并存入Channel中,Channel接受ClientThread的请求并分派请求给WorkerThread进程,WorkerThread是电梯线程类。因为三部电梯的属性都不相同,所以我新建了Elevator来存放电梯的属性。本次作业较难实现的有以下几点:有些乘客的请求一部电梯是无法完成的,考虑到1层和15层三部电梯都能够停靠,自然想到把1层和15层用作换乘楼层,这样每个乘客的请求最多使用两部电梯完成,请求的拆分要注意到时间按问题,我采用的是先把第一段请求处理完毕,如果需要换乘才能到达目标楼层,再把第二段请求放入请求队列request queue。共享对象的同步问题也成为本次作业设计的难点,为了减少三个电梯的依赖相关,对三部电梯分别建立pickupqueue队列,对request queue的访问仍然采用wait和notify实现;这次作业因为有换乘请求的存在,无法对三个电梯分别退出线程,通过判断personrequest、requestqueue、三部电梯的pickupqueue均为null,使用System.exit退出程序;只使用ALS调度,性能会比较低,我在取请求时借鉴了SCAN调度,取离电梯最近的乘客。
Bug分析
这次作业在强测时没有发现bug,但是在互测时被hack一次,竟然是因为最终电梯门没有关。这是因为我退出程序时只是考虑了请求已经处理完毕就退出了程序,没有对电梯的开关门状态进行判断。这次互测没有成功hack同屋的其他同学,测试时使用脚本进行数据的输入,主要从以下几个方面进行测试:对于一个电梯不能送达的乘客,能否正确拆分请求;通过构造同一时间大量请求,观察电梯是否超载;程序处理请求完毕,能否正常退出程序。
代码度量
Complexity Metrics(复杂度分析)
Complexity metrics | 星期一 | 22 四月 2019 23:19:39 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
elevator3.Channel.Channel(int) | 1 | 1 | 2 |
elevator3.Channel.PersonIn(Elevator,int) | 1 | 9 | 9 |
elevator3.Channel.PersonOut(Elevator) | 1 | 5 | 5 |
elevator3.Channel.abs(int) | 2 | 1 | 2 |
elevator3.Channel.distance(int,int) | 2 | 2 | 2 |
elevator3.Channel.getpickupqueue(int) | 1 | 1 | 1 |
elevator3.Channel.judgeend() | 3 | 11 | 12 |
elevator3.Channel.putRequest(PersonDemand) | 1 | 3 | 3 |
elevator3.Channel.setinputend(boolean) | 1 | 1 | 1 |
elevator3.Channel.setopen(int,int) | 1 | 1 | 1 |
elevator3.Channel.startWorkers() | 1 | 2 | 2 |
elevator3.Channel.takeRequest(Elevator) | 1 | 12 | 12 |
elevator3.ClientThread.ClientThread(Channel) | 1 | 1 | 1 |
elevator3.ClientThread.run() | 3 | 4 | 4 |
elevator3.Elevator.Elevator(int,int,int,int,int) | 1 | 1 | 1 |
elevator3.Elevator.changeFloor(int) | 1 | 1 | 1 |
elevator3.Elevator.getFloor() | 1 | 1 | 1 |
elevator3.Elevator.getMax() | 1 | 1 | 1 |
elevator3.Elevator.getMovespeed() | 1 | 1 | 1 |
elevator3.Elevator.getNum() | 1 | 1 | 1 |
elevator3.Elevator.getOrder() | 1 | 1 | 1 |
elevator3.Main.main(String[]) | 1 | 1 | 1 |
elevator3.PersonDemand.PersonDemand() | 1 | 1 | 1 |
elevator3.PersonDemand.PersonDemand(int,int,int) | 1 | 1 | 2 |
elevator3.PersonDemand.abs(int) | 2 | 1 | 2 |
elevator3.PersonDemand.execute() | 1 | 2 | 2 |
elevator3.PersonDemand.findway() | 1 | 5 | 12 |
elevator3.PersonDemand.getFromFloor() | 1 | 1 | 1 |
elevator3.PersonDemand.getPersonId() | 1 | 1 | 1 |
elevator3.PersonDemand.getThrough() | 1 | 1 | 1 |
elevator3.PersonDemand.getToFloor() | 1 | 1 | 1 |
elevator3.PersonDemand.getelevator() | 1 | 1 | 1 |
elevator3.PersonDemand.getnextFloor() | 1 | 1 | 1 |
elevator3.PersonDemand.getupdown() | 1 | 1 | 1 |
elevator3.PersonDemand.index(int) | 1 | 1 | 2 |
elevator3.PersonDemand.setId(int) | 1 | 1 | 1 |
elevator3.RequestQueue.RequestQueue() | 1 | 1 | 1 |
elevator3.RequestQueue.getQueue() | 1 | 1 | 1 |
elevator3.WorkerThread.WorkerThread(String,Channel) | 1 | 1 | 1 |
elevator3.WorkerThread.abs(int) | 2 | 1 | 2 |
elevator3.WorkerThread.index(int) | 1 | 1 | 2 |
elevator3.WorkerThread.run() | 5 | 11 | 12 |
elevator3.WorkerThread.updatefloorduring(int) | 1 | 1 | 7 |
elevator3.WorkerThread.updatefloorfrom(int,PersonDemand) | 1 | 6 | 7 |
Class | OCavg | WMC | |
elevator3.Channel | 2.92 | 35 | |
elevator3.ClientThread | 2 | 4 | |
elevator3.Elevator | 1 | 7 | |
elevator3.Main | 1 | 1 | |
elevator3.PersonDemand | 1.79 | 25 | |
elevator3.RequestQueue | 1 | 2 | |
elevator3.WorkerThread | 3.83 | 23 | |
Package | v(G)avg | v(G)tot | |
elevator3 | 2.89 | 127 | |
Module | v(G)avg | v(G)tot | |
homework_7 | 2.89 | 127 | |
Project | v(G)avg | v(G)tot | |
project | 2.89 | 127 |
Dependency Metrics(依赖度分析)
Complexity metrics | 星期一 | 22 四月 2019 23:19:39 CST | |
---|---|---|---|
Method | ev(G) | iv(G) | v(G) |
elevator3.Channel.Channel(int) | 1 | 1 | 2 |
elevator3.Channel.PersonIn(Elevator,int) | 1 | 9 | 9 |
elevator3.Channel.PersonOut(Elevator) | 1 | 5 | 5 |
elevator3.Channel.abs(int) | 2 | 1 | 2 |
elevator3.Channel.distance(int,int) | 2 | 2 | 2 |
elevator3.Channel.getpickupqueue(int) | 1 | 1 | 1 |
elevator3.Channel.judgeend() | 3 | 11 | 12 |
elevator3.Channel.putRequest(PersonDemand) | 1 | 3 | 3 |
elevator3.Channel.setinputend(boolean) | 1 | 1 | 1 |
elevator3.Channel.setopen(int,int) | 1 | 1 | 1 |
elevator3.Channel.startWorkers() | 1 | 2 | 2 |
elevator3.Channel.takeRequest(Elevator) | 1 | 12 | 12 |
elevator3.ClientThread.ClientThread(Channel) | 1 | 1 | 1 |
elevator3.ClientThread.run() | 3 | 4 | 4 |
elevator3.Elevator.Elevator(int,int,int,int,int) | 1 | 1 | 1 |
elevator3.Elevator.changeFloor(int) | 1 | 1 | 1 |
elevator3.Elevator.getFloor() | 1 | 1 | 1 |
elevator3.Elevator.getMax() | 1 | 1 | 1 |
elevator3.Elevator.getMovespeed() | 1 | 1 | 1 |
elevator3.Elevator.getNum() | 1 | 1 | 1 |
elevator3.Elevator.getOrder() | 1 | 1 | 1 |
elevator3.Main.main(String[]) | 1 | 1 | 1 |
elevator3.PersonDemand.PersonDemand() | 1 | 1 | 1 |
elevator3.PersonDemand.PersonDemand(int,int,int) | 1 | 1 | 2 |
elevator3.PersonDemand.abs(int) | 2 | 1 | 2 |
elevator3.PersonDemand.execute() | 1 | 2 | 2 |
elevator3.PersonDemand.findway() | 1 | 5 | 12 |
elevator3.PersonDemand.getFromFloor() | 1 | 1 | 1 |
elevator3.PersonDemand.getPersonId() | 1 | 1 | 1 |
elevator3.PersonDemand.getThrough() | 1 | 1 | 1 |
elevator3.PersonDemand.getToFloor() | 1 | 1 | 1 |
elevator3.PersonDemand.getelevator() | 1 | 1 | 1 |
elevator3.PersonDemand.getnextFloor() | 1 | 1 | 1 |
elevator3.PersonDemand.getupdown() | 1 | 1 | 1 |
elevator3.PersonDemand.index(int) | 1 | 1 | 2 |
elevator3.PersonDemand.setId(int) | 1 | 1 | 1 |
elevator3.RequestQueue.RequestQueue() | 1 | 1 | 1 |
elevator3.RequestQueue.getQueue() | 1 | 1 | 1 |
elevator3.WorkerThread.WorkerThread(String,Channel) | 1 | 1 | 1 |
elevator3.WorkerThread.abs(int) | 2 | 1 | 2 |
elevator3.WorkerThread.index(int) | 1 | 1 | 2 |
elevator3.WorkerThread.run() | 5 | 11 | 12 |
elevator3.WorkerThread.updatefloorduring(int) | 1 | 1 | 7 |
elevator3.WorkerThread.updatefloorfrom(int,PersonDemand) | 1 | 6 | 7 |
Class | OCavg | WMC | |
elevator3.Channel | 2.92 | 35 | |
elevator3.ClientThread | 2 | 4 | |
elevator3.Elevator | 1 | 7 | |
elevator3.Main | 1 | 1 | |
elevator3.PersonDemand | 1.79 | 25 | |
elevator3.RequestQueue | 1 | 2 | |
elevator3.WorkerThread | 3.83 | 23 | |
Package | v(G)avg | v(G)tot | |
elevator3 | 2.89 | 127 | |
Module | v(G)avg | v(G)tot | |
homework_7 | 2.89 | 127 | |
Project | v(G)avg | v(G)tot | |
project | 2.89 | 127 |
UML协作图
设计原则检查
- SRP(单一责任原则):类和方法的功能行为单一,基本符合该原则。
- OCP(开放封闭原则):这次作业进行重构设计,未能满足此原则。
- LSP(里氏替换原则):没有使用继承。
- ISP(接口分离原则):没有采用接口设计。
- DIP(依赖倒置原则):模块之间的耦合度控制的较小,基本满足该原则。
四、总结与反思
本单元作业逐步递进,难度越来越大。第一次作业比较简单,引入多线程的概念,初步掌握多线程程序的设计方法和代码调试方法;第二次作业在第一次作业基础上进行扩展,增加电梯的功能,对共享对象的访问控制和面向对象的并发设计提出新的要求;第三次作业有一部电梯过渡到三部电梯,进一步考察多线程协同、线程安全类的设计,如何在完成给定功能的基础上进行抽象设计以降低模块的耦合度。第二次作业对第一次作业进行扩展完成设计,第三次作业和第二次相比变动较大,进行重构设计,违背了开闭原则。因此,在设计中要考虑在今后的设计中可能添加哪些功能,实现代码复用,提高开发效率。除此之外也要注意程序的性能,第二次作业由于没有进行性能的优化,导致在强测中的得分不是很高。在调度策略的设计上,不论是ALS还是SCAN都有其适用范围,因此要充分运用各种调度策略,进行优化设计,追求更好的性能。