OO第二单元总结分析
第一次作业
设计策略
第一次的傻瓜电梯设计十分平凡,没有使用任何算法策略
-
类设计:
Elevator
类 : 表示电梯的状态,控制电梯的运行.ElevatorThread(Thread)
类 : 模拟电梯运行的线程类Main(Thread)
类 : 主类控制输入
-
多线程分析:
- 共享对象 : 由
Elevator
类与Main
类共享一个请求队列 - 安全性保障 : 自定义类
RequestQueue
为一个线程安全类,其中的方法都定义为synchronized
的加锁方法 - 设计模式 : 采用生产者-消费者模式来阻止类
- 共享对象 : 由
基于度量分析程序结构
第一次作业结构比较简单,模块和方法规模都比较小
Single Responsibility Principle
: 主类负责读取指令并当放入指令队列中去,电梯线程类读取指令并控制电梯运动,电梯类保存自身状态,比较符合类功能的独立性Open Close Principle
: 可扩展性一般,电梯类可通过继承扩展不同种电梯,但是调度器的扩展性差Liskov Substitution Principle
: 没有使用继承Interface Segregation Principle
: 未定义抽象类Dependency Inversion Principle
: 与客户交互时,只依赖于抽象接口
分析自己的程序bug
第一次作业在公测和互测中均未被发现bug
第二次作业
设计策略
第二次的电梯要求支持随层捎带
-
算法策略 : 使用
SSTF
作为基本策略,详细如下 :-
选择主请求策略 :
- 当电梯中没有乘客时,选择距离自己最近的请求来作为主请求
- 当电梯中有乘客时,选择执行最早完成的请求作为主请求
- 电梯在每一层都检查是否可以更新主请求
-
添加捎带请求策略 :
- 可捎带判定 : 起始于电梯目前层,目标楼层与当前主请求的目标楼层在当前同一侧
- 电梯在每一层检查该层是否有可捎带的指令,若有则加入电梯执行对列
-
电梯运动控制 :
- 电梯的运行控制仅与当前主请求方向有关
- 电梯按层运行
- 电梯的开关门操作取决于是否有可捎带以及是否有乘客出去
-
算法流程图示
-
-
类设计
RequestQueue
类 : 线程安全的请求队列类Elevator
类 : 电梯类,保存电梯状态Passenger
类 : 乘客类,保存乘客的行为Scheduler
类 : 调度器类,负责- 选择主请求.
- 判断是否有捎带请求.
- 给电梯发送运行指令
ElevatorThread(Thread)
类 : 模拟电梯运行的类Main(Thread)
类 : 模拟输入的类
-
多线程分析 :
-
共享对象 : 只有输入线程与模拟电梯线程共享一个请求队列
-
安全性保障 :
- 设计
RequestQueue
本身为线程安全的类 - 此外,由于在调度器中包含大量的逻辑代码来判断主请求和捎带请求,所以在遍历队列来选择的时候要对请求队列加锁,保证逻辑选择的正确性
- 设计
-
设计模式 :
- 输出线程与电梯线程共享请求队列采用生产者-消费者模型
- 电梯与其中的乘客采用订阅者模式
-
基于度量分析程序结构
从分析中可以看出电梯线程的run
方法还是复杂度过高,应该考虑将其中的一些逻辑分离出去,降低其方法的复杂度
Single Responsibility Principle
: 主类负责读取指令并当放入指令队列中去,调度器读取指令分析并发送指令,电梯线程类根据指令进行运动,电梯类保存自身状态,比较符合类功能的独立性Open Close Principle
: 可扩展性在多电梯角度比较好,只需新增加电梯对象即可,但是在调度器任务的可扩展性比较差Liskov Substitution Principle
: 没有使用继承Interface Segregation Principle
: 未定义抽象类Dependency Inversion Principle
: 与客户交互时,只依赖于抽象接口
分析自己的程序bug
在第二次作业中在公测与互层中出现了一个由于逻辑混乱导致的bug
- 产生原因 : 在控制电梯运行时通过比较当前楼层与目标楼层决定运行方向使用
if-else
结构时将二者相等的情况误算入到向下运行导致电梯运行到-4这一不存在的楼层 - 修复 : 在控制层次对三种情况分开讨论即可,即加入相等的情况
第三次作业
设计策略
此次作业在第二次的基础上扩展为3种不同的电梯
-
算法策略(主要是分配指令的算法,单部电梯的运行算法与第二次无异)
由于一些时间关系,我没有采用过于复杂的优化算法,只进行了一些静态的优化算法
-
对指令进行分类 :
-
可以由一步电梯直达的指令
判断进出楼层是否在同一个电梯来决定是否可以直达
- 由于进出层都是奇数层的可能性比较小,所以优先使用
C
电梯 - 然后由于
A
电梯比较快,所以次之使用A
电梯 - 当前2条都不满足时,再使用
B
电梯
- 由于进出层都是奇数层的可能性比较小,所以优先使用
-
必须由两部电梯共同完成的指令
- 分析题目要求我认为设置1,15层为中转层比较合理
- 3层比较特殊只有
C
电梯可以到达所以要特殊考虑 - 要考虑拆分为两条指令后的执行顺序问题,即要保证乘客先出电梯再进行下一步接送
-
-
对控制进行分层
- 第二次设计中的调度器主要负责单个电梯的运行,维护单个电梯的指令队列
- 加入一个总调度器控制三个子调度器,其主要功能是从输入的总指令队列分发指令到三个子调度器的指令队列
-
-
类设计 : 在第二次的基础上添加和修改了部分类
Elevator
类 : 增加名字,可达楼层,最大载客量,运行速度等属性SchedulerAll
类 : 增加总控制器类,负责将指令分发给具体控制电梯运行的子控制器
-
多线程分析:
- 共享对象 : [主线程(控制输入) + 总调度器] 与 三个[电梯线程 + 子调度器] 分别共享一个电梯的执行请求队列
- 安全性保障 : 由于只是量变,本质上还是两个线程共享一个线程安全队列的问题,安全性的保障框架完全继承与第二次作业.
基于度量分析程序结构
- 虽然将单个电梯的一些逻辑已经移出了
run
方法,但是由于电梯的台数的增多,导致其协同逻辑关系过于复杂,外在表现为电梯线程的复杂度过高 - 在分离主调度器和子调度器的调度逻辑时主调度器有轻微的负载,可以考虑将其一部分功能移到子调度器中从而降低其复杂度
Single Responsibility Principle
: 主类负责读取指令并当放入指令队列中去,总调度器负责拆分指令并分配指令给子调度器,子调度器读取指令分析并发送指令,电梯线程类根据指令进行运动,电梯类保存自身状态,比较符合类功能的独立性Open Close Principle
: 可扩展性在多电梯角度比较好,只需新增加电梯对象即可,但是在调度器任务的可扩展性比较差Liskov Substitution Principle
: 未使用继承Interface Segregation Principle
: 未定义抽象类Dependency Inversion Principle
: 与客户交互时,只依赖于抽象接口
分析自己的程序bug
第三次作业在公测与互测中均未被发现bug
测试方法
在电梯单元由于多线程程序的特点导致其bug
有时具有不可复现性,所以我的基本测试策略是:
- 通过大量的自动生成数据集自动化定点投放输入测试+判定结果正确性
- 当发生错误时分析代码逻辑,定位错误原因,再测试出错的一批数据集
该方法有一定的效果 : 例如我在第二次互测末期完成的测试结构的搭建,由于测试的不充分(主要是无法实现定点投放输入)我的第二次作业出现了一个十分明显而且严重的错误也没有被发现,但是第三次更复杂的作业由于充分的测试而没有被发现bug
通过使用该测试,我在第二次发现了其他同学一个逻辑bug
,在第三次测试发现同学一个线程安全bug
心得体会
- 设计模式
- 无论是单线程还是多线程程序,一个好的设计模式都是极为极为重要的
- 在我不熟练的现状下,一定要充分发挥前人千锤百炼的设计模式的优越性,在未来我希望可以有跳出设计模式的范围来灵活使用的能力,但是目前这些精炼的设计模式会使得我的程序更加健壮
- 不要拘泥于使用单个设计模式,使用不同的设计模式来组装各个模块才是正确的打开方式
- 线程安全
- 首先一定要分析出共享对象在其上的共享线程们,这是安全性设计的基础
- 我个人习惯于通过设计一个好的线程安全类保证线程安全,在线程层次不做这一点的考虑
- 要做到上面一点,设计的合理性非常非常重要,你要保证所有在线程层次进行的操作线程安全类都是支持的,但同时这又不能与这个类自身功能的高内聚低耦合相违背,所以有时也不要强求
- 多用
wait
和notify
来实现逻辑上的同步 - 一定一定不要滥用锁