【OO】第二单元作业总结-电梯系列

【OO】第二单元作业总结-电梯系列


 一、三次作业设计策略

(1)第一次作业

   第一次作业采用傻瓜式单电梯调度策略,采用tray类(托盘模式)进行生产者(加入请求)与消费者(电梯处理请求)的交互,使用wait/notify进行线程等待与唤醒,防止死锁发生。但实际上只是采用了生产者与消费者必须交替进行的策略,读入请求后电梯立即处理请求,如果电梯当前正在处理请求则等待电梯空闲后再处理请求。

(2)第二次作业

  相比较第一次作业,第二次作业加入了捎带需求。通读过指导书后我对主请求和被捎带请求的设计并不是很理解,在与同学探讨后,参考现实中的电梯,设计策略改为不区分主请求和捎带请求,设计一个(伪)楼层类来装载请求队列,新请求将被加在对应的出发楼层和到达楼层,在电梯运行的过程中,遍历楼层队列,对符合要求的请求进行处理。与第一次作业不同的是,第二次作业需要真正的多线程并行执行,即生产者与消费者不能严格交替执行,需要实现对共享资源的同步控制。另外,这次作业的另一个设计难点在于如何正确的结束程序,当请求加入完毕后正确的退出电梯线程。由于我开始是当检测到读入结束后,立即结束电梯程序,导致线程极度不安全。需要在检测到读入结束后,等待电梯完成当前待处理需求后,再退出循环。

(3)第三次作业

  第三次作业需要实现三部功能不同的电梯协同处理需求,难度是本单元作业中最大的。我最终的实现方案是最朴素的拆分需求法,在读入需求时将需要换乘的需求拆分入不同的电梯队列中,采用全局队列来控制请求的执行顺序。


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

(1)第一次作业

度量分析:由于第一次作业比较简单,采用单人傻瓜式调度,利用tray类进行简单的交互。方法复杂度和类复杂度都不高。

SOLID原则分析:

SRP(单一责任原则):符合,每个类只承担一种责任。

OCP(开放封闭原则):基本符合,扩展功能可以通过新增代码来实现,不需要修改存在的代码。

LSP(里氏替换原则):没有使用继承。

ISP(接口分离原则):没有使用自定义接口。

DIP(依赖倒置原则):没有使用抽象类。

(2)第二次作业

度量分析:由于第二次作业延用了第一次作业的思路,电梯线程和输入线程通过tray类进行交互。但加入了捎带需求后,电梯类里的几个检查当前楼层状态的方法和楼层list的方法的基本复杂度都很高。考虑到请求人员的ID唯一,利用ID信息来作为人员状态转变的标记和电梯是否结束运行的标记,采用if-else语句进行复杂逻辑判断,导致checkid方法的基本复杂度,模块设计复杂度,圈复杂度都很高,难以维护和扩展。

SOLID原则分析:

SRP(单一责任原则):不符合,tray类同时实现了放入请求和处理请求(将新增请求放入楼层队列的功能),应当拆分为两个类。

OCP(开放封闭原则):基本符合,扩展功能可以通过新增代码来实现,不需要修改存在的代码。

LSP(里氏替换原则):没有使用继承。

ISP(接口分离原则):没有使用自定义接口。

DIP(依赖倒置原则):没有使用抽象类。

(3)第三次作业

度量分析:可以很明显的看出,相比前两次作业,第三次作业的整体复杂度更高。这次作业的一个失误是没有将电梯创建为一个模板,直接创建三个线程,而是复制了三份,分别创建电梯线程,实际上这三个电梯类是一模一样的。根据度量分析,电梯类的几个方法的复杂度都很高。由于需要与一个全局的ID队列进行信息交互,对ID信息进行创建和修改,导致与判断当前楼层请求队列信息的几个方法的复杂度都很高,很难维护。而在tray类里,需要同时承担加入请求与请求处理两个职责,进行共享资源的处理,其中getElist(将需求加入不同的电梯请求队列里)和checkNum方法的复杂度都很高,依赖外部信息的改变来做相应的处理,复杂度偏高。在类复杂度分析中,tray类和elevator类的复杂度都很高。

SOLID原则分析:

SRP(单一责任原则):不符合,tray类同时实现了放入请求和处理请求(合理拆分需求后将新增请求放入对应楼层队列),应当拆分为两个类。

OCP(开放封闭原则):不符合,tray中的处理请求功能完全依赖于当前请求处理的设定,无法扩展。

LSP(里氏替换原则):没有使用继承。

ISP(接口分离原则):没有使用自定义接口。

DIP(依赖倒置原则):没有使用抽象类。


二、程序bug分析

(1)第一次作业

公测和互测都没有发现bug。

(2)第二次作业

公测没有查出bug,互测发现了bug。

错误分析:没有考虑输入结束时电梯还有未处理完的请求的状况。在得到输入请求结束标记时判断电梯是否处理完当前需求,等待电梯处理完当前正在处理的需求后退出循环。

(3)第三次作业

公测有一半的点由于rtle挂掉,互测发现的bug也均为rtle。

错误分析:在查看错误样例和输出结果后,发现均为电梯处理完需求后无法退出循环,不停的在最大层和最小层之间arrive,无法退出循环,结束线程。事实上,这个错误是由于线程的不安全导致的,在对全局ID队列进行修改时,没有加锁,导致换乘电梯无法正确处理换乘请求。


三、发现别人程序bug所采用的策略

这三次作业我都没有抛大量的数据进行测试,主要还是在阅读代码,理解对方的设计思路和实现方法。不同于上一单元的作业,本单元由于多线程设计的原则,导致程序出bug的例子很难复现。前两次作业的同组代码都很优秀,没有发现bug。第三次作业的bug都是由于线程不安全而导致程序出错。


四、心得体会

这三次作业,让我从对多线程有了更深的理解。最磨人的当属保证线程的安全性,第二次作业和第三次作业都踩了不少的坑。我认为,线程安全主要在对共享资源的修改上,不同线程在处理时必须保证共享资源的安全性,防止在修改过程中读取。

后两次的作业都是沿袭了第一次作业的电梯模型的构建,电梯类没有完完全全的重构。这三次作业中我比较疑惑的便是调度器的设计,为什么需要调度器来进行请求处理。实际上,在我自己的设计中,tray类中已经实现了伪调度器,即处理请求的功能,承当了两种责任。在设计一个类的功能和方法时,应该符合面向对象的SOLID原则,才能进行好的拓展。

除此之外,还应当对自己的程序进行充分的测试。在第三次作业中,我直接参考了第二次作业的电梯设计,重构了需求处理和换乘电梯处理当前需求顺序的功能。在完成后简单的测了几个样例后就交了中测,第一次就全过了(一次AC火葬场)。我便以为经过了第二次作业强测和互测的洗礼,程序应该是没有问题的,但实际上线程还是不安全的,导致了第三次作业强测爆炸。所以,对于自己的程序 ,还是应当进行覆盖性的测试,尽量减少程序中的bug。

猜你喜欢

转载自www.cnblogs.com/MHKING/p/10743411.html