OO第二单元总结与分析

第二次博客作业

(一)从多线程的协同和同步控制来分析自己的设计策略

这个单元的作业让我们了解了多线程并学会了熟练使用,而在使用过程中,所以设计策略,首先应该保证程序的正确性,其次再去保证性能的优化。首先,从需求来分析,给你请求,要求实现乘客的准确到达,这就需要电梯的正确调度和运行。

而第一次作业,我在结构设计上采用的是“生产者-消费者模式”,即将输入当成“生产者”,将NeedQue请求队列当成“盘子”,将电梯当作“消费者”。InputHandler加入请求,调度器Dispatcher每次从盘子也就是请求队列里取出一个请求给电梯,电梯将其运送到指定的位置。输入、调度器和电梯,所以共三个线程一起运行。

第二次作业,由于加带了捎带策略,所以,我在第一次作业的基础上进行了改进,在调度器Dispatcher里直接将请求队列存起来,而数据结构,我选择的是hashmap嵌套ArrayList,分为上行的upmap,下行的downmap。这样有点类似于实现了楼层类。为什么这么说呢?请求队列的hashmap的key,为抵达的楼层,而电梯的hashmap的key是乘客要去的目的楼层,这样就判断是否捎带,以及是否开关门变得十分方便。而整体架构仍然为“生产者-消费者”模式,不过对于Dispatcher使用了单例模式,并采用notifyAll和wait的方式实现同步控制。

第三次作业,保留了不少上一次作业的设计,正如,数据结构,仍然与第二次相同。但由于不同电梯有不同的到达层数,所以一定存在换乘,这导致电悌的调度和运行的算法有些冗杂。我的设计是,设置mid,每次输入,如果必须换乘则mid不为零,而是根据一定策略,制定换乘中转站。

(二)基于度量来分析自己的程序结构

S:单一责任原则:对象应该仅具有单一的功能

O:开放封闭原则:软件体应该对扩展是开放的,但对修改封闭的

L:里氏替换原则:程序中的对象应该是可以在不改变程序正确性的前提下
被它的子类对象所替换的

I:接口分离原则:多个特定客户端接口要好于一个宽泛用途的接口

D:依赖倒置原则:高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;
抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口。

第一次作业类图及度量:

img1

Method ev(G) iv(G) v(G)
Dispatcher.Dispatcher(NeedQue) 1 1 1
Dispatcher.run() 3 3 5
Dispatcher.startElevator() 1 1 1
Elevator.Elevator(int) 1 1 1
Elevator.close(int) 1 2 2
Elevator.move(int) 1 2 2
Elevator.open(int,int) 1 3 3
Elevator.run() 1 1 1
Elevator.setEndFloor(int) 1 1 1
Elevator.setId(int) 1 1 1
Elevator.setStartFloor(int) 1 1 1
InputHandler.InputHandler(NeedQue) 1 1 1
InputHandler.run() 3 4 4
Main.main(String[]) 1 1 1
NeedQue.add(PersonRequest) 1 1 1
NeedQue.change() 1 1 1
NeedQue.getState() 1 1 1
NeedQue.isEmpty() 1 1 1
NeedQue.pop() 1 1 1
Class OCavg WMC
Dispatcher 1.67 5
Elevator 1.12 9
InputHandler 2 4
Main 1 1
NeedQue 1 5

通过分析类图,metrics图,我发现第一次作业中平均循环复杂度和总循环复杂度较好,紧密程度和循环度也正常,其他也较为正常。

所以缺点在于我刚开始接触多线程,所以还不太会使用wait和notify,而且电梯里没有乘客队列,所以不适用于第二次作业,可拓展性较差,而优点在于简练,保证了正确性不会出错。

SOLID分析

单一责任原则:基本满足

开放封闭原则:基本满足

里氏替换原则:基本满足

接口分离原则:基本满足

依赖倒置原则:基本满足

第二次作业作业类图及度量:

img2

Method ev(G) iv(G) v(G)
Dispatcher.add(PersonRequest,boolean) 1 2 2
Dispatcher.addMap(PersonRequest,HashMap<Integer, ArrayList>,int) 1 2 2
Dispatcher.check() 1 3 4
Dispatcher.find(int,boolean) 2 2 2
Dispatcher.getInstance() 1 1 3
Dispatcher.getState() 1 1 1
Dispatcher.ifContains(int,boolean) 2 2 2
Dispatcher.isEmp(boolean) 2 2 2
Dispatcher.isEmpty() 1 2 2
Dispatcher.scanf(int) 1 3 5
Elevator.Elevator(int) 1 1 1
Elevator.close() 1 2 2
Elevator.execution() 1 4 4
Elevator.in() 2 3 4
Elevator.movedown() 1 2 3
Elevator.moveup() 1 2 3
Elevator.open() 1 2 2
Elevator.out() 1 3 3
Elevator.run() 1 6 7
InputHandler.getInput() 1 1 1
InputHandler.run() 3 4 5
Main.main(String[]) 1 1 1
Class OCavg WMC
Dispatcher 2.2 22
Elevator 2.67 24
InputHandler 2.5 5
Main 1 1

通过分析类图,metrics图,我发现第二次作业平均循环复杂度和总循环复杂度较为正常,紧密程度和循环度也较为正常,虽然较第一次作业有所提高,但是这是由于增加了基于捎带的一些设计,所以整体表现仍是OK的。

优点在于使用了hashmap嵌套ArrayList结构来负责存储电梯和调度器中的请求队列,其中不少方法在第三次作业中可以在加以改进后使用,而且捎带正确性有良好保证,电梯没有出错。
而缺点在于,可拓展性的确有待加强,电梯与调度器的关系不太好,没有做好第三次作业的充足准备,而且仅有正确性,性能分得分很低。

SOLID分析

单一责任原则:基本满足

开放封闭原则:可拓展性较差,不适合多部电梯

里氏替换原则:基本满足

接口分离原则:基本满足

依赖倒置原则:Elevator和Dispatcher的关系有待改善,Elevator依赖于Dispatcher检查是否队列非空,无法适用于多电梯。

第三次作业类图及度量:

img3

Method ev(G) iv(G) v(G)
Dispatcher.checkElevator() 1 3 3
Dispatcher.chooseEle() 6 5 12
Dispatcher.getInstance() 1 1 3
Dispatcher.register(int,Elevator) 1 1 1
Dispatcher.run() 4 11 13
Dispatcher.scanf(int,HashMap<Integer, ArrayList>,int) 7 7 10
Dispatcher.stillRun() 2 2 3
Elevator.Elevator(int,int,int,String) 1 1 1
Elevator.addPeople(Member) 1 3 3
Elevator.chooseChange(int) 6 9 13
Elevator.chooseChange2() 7 5 9
Elevator.close() 1 1 1
Elevator.execution() 1 4 4
Elevator.getFloor() 1 1 1
Elevator.getFloorList() 1 1 1
Elevator.in() 3 5 7
Elevator.initFloor(int) 1 1 1
Elevator.isEleEmpty() 1 1 1
Elevator.isFull() 1 1 1
Elevator.isReach(int) 1 1 1
Elevator.isUp() 1 1 1
Elevator.isWait() 1 1 1
Elevator.moveToAim() 1 5 5
Elevator.movedown() 1 3 4
Elevator.moveup() 1 3 4
Elevator.open() 1 2 2
Elevator.out() 1 4 4
Elevator.run() 3 7 8
Elevator.searchChange(int) 6 4 6
Elevator.setAim(int) 1 1 1
Elevator.setChange(Member) 4 4 6
Elevator.setUp(boolean) 1 1 1
Elevator.setWait(boolean) 1 1 1
Elevator.turnBack(Member) 1 1 1
InputHandler.getInput() 1 1 1
InputHandler.run() 3 4 4
Main.main(String[]) 1 1 1
Member.Member(int,int,int) 1 1 1
Member.getEnd() 1 1 1
Member.getId() 1 1 1
Member.getMid() 1 1 1
Member.getStart() 1 1 1
Member.getUp() 1 1 1
Member.setEnd(int) 1 1 1
Member.setMid(int) 1 1 2
Member.setSec() 3 3 13
Member.setStart(int) 1 1 1
Member.setUp() 1 1 2
NeedQue.add(Member) 1 2 3
NeedQue.find(int,boolean) 1 1 2
NeedQue.getDownmap() 1 1 1
NeedQue.getKey() 1 1 1
NeedQue.getUpmap() 1 1 1
NeedQue.ifContains(int,boolean) 2 2 2
NeedQue.isEmp(boolean) 2 2 2
NeedQue.isEmpty() 1 2 2
ReachList.ReachList() 3 10 12
ReachList.getCha(int) 1 1 1
ReachList.getEle(int) 1 1 1
ReachList.getEndKey() 1 1 1
ReachList.getKey() 1 1 1
TheEnd.TheEnd() 1 1 1
TheEnd.addEleEnd(int) 1 1 1
TheEnd.getEleEnd(int) 1 1 1
TheEnd.run() 1 3 4
TheEnd.setEnd(boolean) 1 1 1
Class OCavg WMC
Dispatcher 5.29 37
Elevator 2.96 80
InputHandler 2 4
Main 1 1
Member 1.64 18
NeedQue 1.62 13
ReachList 2.8 14
TheEnd 1.4 7

显然,通过分析类图,metrics图,我发现与前两次作业对比,第三次作业耦合度高了不少,出现了标红区域,这是前两次未曾出现的。

原因主要是自己的调度策略写的比较复杂,优化不够好,不够简洁,多层循环导致平均循环复杂度和总循环复杂度提高。

优点在于仍然使用了hashmap嵌套ArrayList结构来负责存储电梯和调度器中的请求队列,对于换乘,有着正确的处理思路。而缺点在于,可拓展性很差,很多地方都固定死了电梯数量,而且出现了正确性错误,在电梯的run方法里,出现了轮询现象,而且性能分得分也很低。虽然分类较为合理,但Dispatcher和Member以及Elevator里存在些选择和调度策略有关的方法,这些方法冗长而复杂,造成了程序耦合度和复杂度的增加,而且容易出错。

SOLID分析

单一责任原则:电梯责任较为复杂,判断是否捎带较为愚蠢,出现了先拿进电梯队列有投回去的情况

开放封闭原则:可拓展性较差,不适合增加电梯

里氏替换原则:基本满足

接口分离原则:基本满足

依赖倒置原则:基本满足

(三)分析自己程序的bug

第一次作业没有bug,电梯运行逻辑比较简单,但是没有使用wait和notify,不太友好。

第二次作业并没有bug,电梯运行正常,只是捎带性能较差。

虽然经过两次作业,但是我对线程安全的理解其实仍然不太到位,有些地方是比较模糊的。

所以,第三次作业,非常惨,出现了致命的错误:在elevator的run方法里出现了轮询问题,emmm,正如:
if(条件){
wait();
} else{ },
else里如果执行时间很短,则会出现轮询问题,所以需要sleep操作。这个bug是致命的,会导致cpu运行时间超时,导致我的多个测试点超时,而且这样的线程是不安全的。所以我在bug修复环节解决这个问题后,所有测试点便得以通过了。

(四)分析自己发现别人程序bug所采用的策略

感觉多线程的话,一定要学会自动化评测的手段。因为自己手动在控制台输入,是不可能定点投放请求的。所以必须有自动化评测手段,比如用jar打包+命令行的方式来实现。

而多线程下,电梯运行可能出现的问题有很多,比如:线程无法正常停止;有乘客未被接上就结束了线程;“吃人”电梯(有乘客滞留电梯); 数组下标越界;线程停止时开关门操作异常等。

那如何找别人的bug呢?我觉得自己首先要通读别人的代码,先理解别人的代码和设计思路,看能否直接找到错误,构造相应测试点,如果逻辑上没有找到问题,可以自己构造一些临界或易错的测试点,用自动评测化手段,多次测试,看是否出现问题。

(五)心得体会

这个单元的作业主要是让我了解了多线程的一些设计模式和方法,并在电梯的设计中,学会了熟练使用这些方法,在踩雷和写出bug的过程中,认识到了线程安全的重要性。

线程安全方面来说,我体会到了多线程中线程安全的重要性,学会了采用notifyAll和wait的方式实现同步控制,用synchronized锁来保证线程之间互斥访问共享对象,而且设计中一定要避免轮询等问题。
从设计原则方面来说,这次最熟练的使用便是“生产者-消费者模式”了,除了以外,我们还应该学会减少硬编码,并且能够灵活地使用工厂模式、抽象工厂模式等好的设计模式或方法。

posted @ 2019-04-24 19:45  Puzzled_Bubble  阅读(162)  评论(1)    收藏  举报