OO第二单元总结
OO第二单元总结
1. 基于多线程的协同和同步控制的设计策略分析
由于三次作业的多线程设计方法和电梯调度策略相似,所以一起陈述
1.1 多线程设计方法
本单元三次作业采用的都是 生产者—消费者
模式,将输入请求作为生产者,将调度器作为共享对象,将电梯作为消费者。
1.2 生产者
生产者线程进行输入的管理,使用ElevatorInput,负责接受请求并存入队列。同时,还负责向调度器发送输入结束信号。
1.3 调度器
调度器用于管理请求,包含输入的请求队列,和电梯进行交互并指派任务给电梯,需要保证调度器是线程安全的。当有多部电梯等待且出现新请求时,是让符合条件的电梯进行争夺,不作特别的调度。
1.4 消费者
由于三次作业都是可捎带电梯,所以采用的电梯调度策略都是look调度策略
,具体的电梯调度策略可见这篇博客:https://blog.csdn.net/NickHan_cs/article/details/105551234
构建多个电梯线程,多部电梯统一建模,各自进行个性化配置并维护状态,各自负责和调度器进行交互、接受任务,各自基于接收到的任务进行执行。
2. 基于功能设计与性能设计平衡的可扩展性分析
关于面向对象程序设计的设计原则的基本介绍可以参考这篇博客:
https://blog.csdn.net/NickHan_cs/article/details/105551234
-
SRP——单一职责原则
在SRP原则上,我的电梯没有满足这个原则,尤其是第三次作业中,但电梯的run方法和调度器类的耦合太多,电梯除了基本的接收请求、上下行、进出人、开关门等,还向调度器中删除和新增请求、捎带请求的判断等,导致电梯类容易的逻辑难以封闭。如果任务要求有所改动,调度器和电梯类会出现连锁反应,可扩展性较差。
-
OCP——开闭原则
在OCP原则上,前两次作业做的相对好,从第一次作业到第二次作业几乎没做什么改动就完成了,但是第三次作业新增了中转、停靠楼层等限制,就导致我的代码需要做较大的改动,类的方法都有了很大的变动,可以设想如果还要新增功能,还需要大幅度的改动。究其原因,就是方法没有做到“高内聚、低耦合”,方法与方法之间的耦合太多,应该使用好接口好抽象类等手段,使程序易于维护和升级。
-
LSP——里氏代换原则、ISP——接口隔离原则、DIP——依赖倒置原则
本单元的三次作业都没有涉及继承和接口,所以不涉及这些原则的问题。但也恰恰因为没有使用,会导致程序的抽象化不够,可扩展性不强。
3.基于度量的程序结构分析
3.1 度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模
3.1.1 第一次作业
类名 | 属性个数 | 方法个数 | 类总代码规模 |
---|---|---|---|
MainClass | 0 | 1 | 10 |
People | 1 | 2 | 25 |
ReqList | 2 | 9 | 68 |
Elevator | 5 | 11 | 136 |
MainClass类 | People类 | ||||
---|---|---|---|---|---|
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
main | 8 | 0 | People | 3 | 0 |
run | 17 | 1 |
ReqList类 | Elevator类 | ||||
---|---|---|---|---|---|
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
ReqList | 4 | 0 | elevatorSleep | 7 | 0 |
add | 13 | 2 | getThisToFloor | 8 | 1 |
get | 3 | 0 | Elevator | 3 | 0 |
remove | 13 | 1 | run | 29 | 5 |
getThisFromFloor | 9 | 1 | travelForIn | 13 | 1 |
InputEnd | 4 | 0 | travelForOut | 10 | 1 |
getInputEnd | 3 | 0 | openDoor | 5 | 0 |
isEmpty | 3 | 0 | closeDoor | 4 | 0 |
size | 3 | 0 | getInEle | 4 | 0 |
getOutEle | 4 | 0 | |||
move | 32 | 6 |
3.1.2 第二次作业
类名 | 属性个数 | 方法个数 | 类总代码规模 |
---|---|---|---|
MainClass | 0 | 1 | 16 |
People | 1 | 2 | 26 |
Requests | 3 | 11 | 81 |
Elevator | 8 | 11 | 144 |
MainClass类 | People类 | ||||
---|---|---|---|---|---|
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
main | 14 | 0 | People | 3 | 0 |
run | 18 | 1 |
Requests类 | Elevator类 | ||||
---|---|---|---|---|---|
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Requests | 3 | 0 | elevatorSleep | 7 | 0 |
add | 13 | 2 | getThisToFloor | 8 | 1 |
getFirstFromFloor | 6 | 1 | Elevator | 4 | 0 |
getLastFromFloor | 6 | 1 | run | 29 | 4 |
getThisFromFloor | 9 | 1 | travelForIn | 18 | 3 |
remove | 11 | 1 | travelForOut | 10 | 1 |
setElevatorNum | 4 | 0 | openDoor | 5 | 0 |
getElevatorNum | 3 | 0 | closeDoor | 4 | 0 |
setInputEnd | 4 | 0 | getInEle | 4 | 0 |
getInputEnd | 3 | 0 | getOutEle | 4 | 0 |
isEmpty | 3 | 0 | move | 29 | 6 |
3.1.3 第三次作业
类名 | 属性个数 | 方法个数 | 类总代码规模 |
---|---|---|---|
MainClass | 0 | 1 | 9 |
Client | 1 | 3 | 58 |
Channel | 4 | 12 | 106 |
Req | 4 | 5 | 29 |
Elevator | 13 | 11 | 189 |
MainClass类 | ||
---|---|---|
方法名 | 方法规模 | 方法控制分支数目 |
main | 7 | 0 |
Client类 | Req类 | ||||
---|---|---|---|---|---|
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Client | 3 | 0 | Req | 6 | 0 |
transitJudge | 19 | 6 | getId | 3 | 0 |
run | 30 | 3 | getFromFloor | 3 | 0 |
getToFloor | 3 | 0 | |||
getFinalFloor | 3 | 0 |
Channel类 | Elevator类 | ||||
---|---|---|---|---|---|
方法名 | 方法规模 | 方法控制分支数目 | 方法名 | 方法规模 | 方法控制分支数目 |
Channel | 9 | 0 | elevatorSleep | 7 | 0 |
startWork | 5 | 0 | getToThisFloor | 8 | 1 |
addElevator | 5 | 0 | Elevator | 33 | 2 |
getFromThisFloor | 10 | 1 | run | 29 | 4 |
hasMoreReq | 18 | 3 | travel4In | 18 | 3 |
setInputEnd | 4 | 0 | travelForOut | 10 | 1 |
getInputEnd | 3 | 0 | openDoor | 5 | 0 |
add4AllReq | 3 | 0 | closeDoor | 4 | 0 |
remove4AllReq | 9 | 1 | getInEle | 4 | 0 |
addPerson | 4 | 0 | getOutEle | 9 | 1 |
removePerson | 15 | 0 | move | 35 | 8 |
isEmpty | 3 | 0 |
3.2 经典OO度量
3.2.1 第一次作业
3.2.2 第二次作业
3.2.3 第二次作业
3.3 类图
3.3.1 第一次作业
3.3.2 第二次作业
3.3.3 第三次作业
3.3.4 类图分析
-
缺点
部分类中的方法过于集中,导致类的复杂度过高,比如电梯的run方法既有捎带的判断又要根据调度策略判断移动方向,会导致这个方法与其他方法的耦合度过高。其实,在调度器分派任务的时候,还可以做进一步的优化,增加一个二级调度器等来降低耦合度和优化性能。
在抽象化上做的不够好,当程序需要增加更多功能,需要重构的部分较多,会变得难于扩展和升级,应该运用继承和接口等抽象化方式让程序结构更加易于扩展。
-
优点
本单元在程序结构上较为清晰也十分相似,尤其是前两次作业的结构一模一样,最后一次作业因为涉及到中转,所以在原先的基础上作了一定的调整,但由于设计模式都是
生产者-消费者
模式,所以结构也十分相似,因此在迭代时十分容易。
3.4 UML协作图
4. 分析自己程序的bug
本单元只在第一次作业时被hack了,问题在于消费者线程的run方法,特征是采用了暴力轮询来观察是否有新的请求产生,导致CPU运行超时。应该采用wait()
和notifyAll
来解决轮询的问题,实现多线程的协调运行。
5. 分析发现别人程序的bug策略
本单元只在第二次作业互测时hack成功了,hack成功率7/56,采用了针对性测试和随机性测试两种策略。
-
针对性测试:根据一些容易出bug的边界情况设置有针对性的边界数据,比如第一次作业自己被hack的CPU运行超时问题和电梯的限载人数问题等。
-
随机性测试:编写程序随机构建测试代码,同时还可以根据需求调整请求的分布。
本单元的测试策略与第一单元测试策略上主要有两点不同:
- 本单元由于引入了多线程,所以可以针对线程安全构造一些数据,来发现别人程序是否有死锁等问题;
- 本单元使用了程序来构建随机数据。
6. 心得体会
本单元是针对多线程程序设计的,所以如何保证线程安全是一个很重要的问题。部分同学在调用同步方法的时候出现了死锁等问题,我可能运气比较好,在第一次作业的时候就只用到了一个对象锁,而且采用了生产者-消费者的设计模式,架构比较简单,因此在之后的迭代中也都没有出现死锁的现象。但是,伴随着多线程的复杂,很有可能出现线程不安全的问题,因此就这个问题还需要进一步的研究。
从设计原则的角度分析,我在面向对象程序设计与构造上一个很大的问题就是几乎没用到继承和接口等抽象化手段,而这样导致的结果就是程序的可扩展性很差,难于维护和升级。当然,SOLID原则的要求还是很高的,路漫漫其修远兮,我离这些要求还有很长的距离,在日后的OO程序设计中,要多针对接口和抽象类编程,用好继承等OO特有的方法。