OO第二单元总结
设计策略
在三次作业中,我的架构基本保持一致。使用生产者消费者设计模式,输入线程作为生产者,电梯类作为消费者,调度器类作为缓冲区,使用锁保护调度器类的所有方法。电梯类中的run方法,每一次循环我视为电梯移动一层,使用了look算法,当电梯停止的时候,检查电梯是否有请求,如果没有则wait,等待输入线程notify,如果有则将方向改为有请求的方向,然后除非电梯运行方向上的楼层没有请求并且此时在电梯内的乘客不需要电梯再继续往运行方向上移动的时候改变电梯的方向。
在第三次作业中,由于涉及到换乘,电梯类被我改造成了同时作为生产者和消费者。原本的请求也进行了改造,PersonRequest类被拆分成多个自请求用SubRequest类储存。所以电梯再弹出请求时也会变成生成者,这部分内容和输入线程共用调度器的同一个接口。
关于线程的结束,显然当多电梯的情况下,要所有电梯都运行结束且输入线程结束才可以结束程序。所以我直观想到的办法就是在调度器类中设置一个变量BreakFlag,当输入线程结束的时候,将这个变量置1,且notifyAll,使得所有电梯线程重新开始检查请求,每当一个电梯陷入wait状态便使得一个变量++,当这个变量为所有电梯总数的时候结束程序,exit(0)
。
架构设计可扩展性
在第三次作业中,我设计的电梯类,只负责电梯该做的事情,即根据当前的运行方向进行上下楼,停在某楼层时进行上下人。其余的包括方向控制,哪些人可以搭乘该电梯,哪些人不能搭乘该电梯,全部交给调度器来进行控制。并且,调度器并不直接读取电梯运行过程中的信息,虽然这样不利于性能的优化,但是在数据的安全性可扩展性方面还是有一定优势的。如果此时再对电梯加上其他要求,一般情况下只需要对调度器增加相关方法,并且在电梯类中调用即可。但是在第三次作业多种电梯的情况下,我没有将三种电梯单独分类作为电梯类的子类,现在想想还是考虑不周到,不利于其的扩展性。不过我的电梯架构三次作业基本没有变化,相比于上一单元每次作业都是一次重构来说,本单元的作业基本是迭代开发。所以我认为我的电梯在可扩展性方面,虽然有很多不足,但是相比上一单元的作业还是有了改善。
单一职责原则(Single Responsibility Principle)
这次作业中,我引入了较少的类。大部分功能都集成到了调度器类中,所以在单一职责原则方面做的不够好。这是我在设计时没有考虑周全的地方,或者说为了写起来方便,降低扩展的复杂度,减少出错的概率我有意删减了一些类。
开闭原则(Open Closed Principle)
如果修改是关于电梯已有的功能本身,那不可避免地要调整现有架构。
里氏替换原则(Liskov Substitution Principle)
在本次作业中我没有设计电梯类的继承关系,由于本次作业三种电梯的所有区别仅仅是在数据上,在功能上大致相同,所以我的做法是在电梯初始化的时候,根据类型将电梯中的变量变为不同的数值。满足LSP原则。
接口隔离原则(Interface Segregation Principle)
本次作业并没有用到接口,但是类与类之间的方法功能基本是分开的,基本满足ISP原则。
依赖倒置原则(Dependence Inversion Principle)
该原则在本次作业中没有具体的体现。
程序结构分析
第一次作业
类图
度量分析
第二次作业
类图
度量分析
第三次作业
类图
度量分析
BUG
在第一次作业中,强测没有出现bug,互测中,由于对多线程理解不够深入,出现了死锁的bug,我在电梯类里面,当时我是想保证其检查是否要改变方向的时候,保持整个方法运行时候的一致性,于是我在电梯类里运行那些方法的时候对build类加了锁,然后,电梯类获得build类的锁之后,然后电梯没有获得dispatcher类的锁,此时突然给出输入,我的addRequest方法获得了dispatcher的锁。但是,request是要加到build类里面的,但是它没有build的锁,锁被电梯类获取了,电梯类也获取不到dispatcher的锁,被addRequest获取了,就这样死锁了。
除此之外,其余包括我在试图通过中测的过程中没有出现过其他的多线程问题。
第二次作业中强测互测都没有出现问题。
第三次作业中由于我通过了中测之后因为一些原因没有继续进行测试,导致我出现了一些严重的bug,从而没有进入互测。这个bug过于沙雕,和多线程本身没有任何关系,纯属手滑。修改4行之后所有数据点一波带走,所以在此处不再赘述。
心得体会
通过本单元的作业,我对多线程程序有了一个初步的了解,并了解了一些简单的互斥访问与同步控制的方法。以及一些多线程程序中常用的设计模式。