OO第二单元作业总结

OO第二单元总结


18231169 黄思为


前言

​ 吸取了第一单元的经验教训,在完成这一单元的作业时,我在动手敲代码前,先进行架构的设计,并在稿纸上画出大致的类图。唯结果论来说,这起到了一定效果,本单元的作业相比第一单元,有了很大的进步。当然我认为有部分原因也在于,第二单元主要是训练多线程的处理,与第一单元相比,没有那么多情况与细节,这也让我这种“糙汉子”少了很多烦恼。不管怎么说,在第二单元的作业中,算是取得了一个让自己满意的成绩。

1.作业内容概述

1.1第一次作业

​ 第一次作业要实现单部简单电梯,停靠所有楼层,无载客容量,性能分考量电梯运行时间。

1.2第二次作业

​ 第二次作业实现多部电梯,电梯数量由初始化设定,每部电梯都停靠所有楼层,有相同载客容量上限,性能分考量电梯运行时间。

1.3第三次作业

​ 第三次作业实现多部电梯,初始三部,可通过指令动态增加。共分为三类电梯,三类电梯停靠楼层、载客上限、运行时间(上下及开关门)均有所不同。性能分考量电梯运行时间与乘客等待时间。

2.设计策略

2.1第一次作业

​ 第一次作业较为简单,我采取了经典的“生产者-消费者”模式。接收输入线程为生产者,电梯为消费者,中间类为包含请求队列的调度器。电梯每到一层便与调度器交互,取出可以捎带的请求。若电梯内无请求,则向调度器申请一个主请求,并判断捎带。请求队列中无请求时,电梯线程阻塞。

2.2第二次作业

​ 第二次作业,加入了多部电梯的设定。为了追求更好的性能,我查找了电梯群控策略的相关文献。我发现由于我们的电梯性能判定与实际电梯运行还是有些差别,比如无需考虑乘客等待时间(当然第三次就加入了这项指标),所以不需要用到特别复杂的策略。我参考了一些文献,最后确定了一种类似“观察者模式”的架构。首先是输入线程接收控制台输入,发送给被观察者CentralController。与第一次作业不同,中间类CentralController不保存请求,当它接收到一个请求,就将请求广播给所有电梯,电梯判断自己执行该请求所需开销(主要是能开始执行该请求的时间)。中控器选择开销最小的电梯分配请求,请求保存在电梯的waitingList中。同时,由于载客上限的设定,我加入了“拒载”的行为,如果电梯内人数与请求队列中人数高于一个阈值,电梯便拒绝接受请求。若所有电梯都拒载,则顺序选择一部电梯投放请求。

​ 今天通过老师的讲解,我认为这次作业的设计中似乎存在着在一个线程调用另一个线程方法的情况。当广播请求是由输入线程调用,而广播时需要调用电梯分析请求的方法。这可能不是一个好的设计。

2.3第三次作业

​ 第三次作业中,由于涉及到换乘问题,我就抛弃了第二次的“类观察者模式”,重新回归经典“生产者-消费者”模式。最后我的设计也比较简单,首先是对需要换乘的请求进行拆分,加入全局请求队列,与第一次作业设计类似,电梯每到一层进行一次交互,取出可捎带的请求。基本上是一种“无为而治”的方法。由于本次性能分加入了乘客等待时间的考量,而这种调度策略与FCFS比较类似,所以性能分也比我想象得高一些。

3.扩展性分析

​ 第三次作业可以说已经是前两次作业的扩展,所以扩展性分析直接从第三次作业入手。

3.1SRP——单一职责原则

​ 在第三次作业的设计中,我认为自己设计的每个类职责还算较为明确。主类用于创建和开始进程。输入类读取控制台输入,放入全局请求队列。中控类负责管理请求队列,为电梯提供请求。电梯类可向中控类获取请求,并运行、处理请求。但是也存在一些问题,我设计了Passenger类,我的请求输入后便被解析为Passenger。这个类的职责主要是保存请求内容,并根据请求内容与电梯状态判断能否进出电梯,同时完成进出电梯的操作。不知道Passenger类的设计会不会有些许冗余?这些事是不是可以交给中控类去做?(在上面的分析中,中控类的职责是较少的)

3.2OCP——开闭原则

​ 我认为自己的设计不能很好地满足开闭原则。由于策略比较简单,由电梯自行去申请请求,如果将来有更多的要求,可能无法满足。比如要关闭一部电梯?可能就不能做到很好地交互。所以在OCP方面,设计有所欠缺。

3.3LSP——里氏替换原则

​ 在不同类型的电梯的设计上,我采取了继承的策略。但是我不确定自己的这种继承是否合理。我的设计是父类电梯写好了所有的方法,对于不同的电梯,只是初始化时,构造函数有所不同,如:停靠楼层使用HashSet管理,运行时间、开关门时间则是定义了一些浮点数。虽然继承可能存在一些不合理,但是我认为还是能够满足LSP的,因为我的设计中,子类继承父类并没有新增属性和方法,只是初始化参数不同。

3.4ISP——接口隔离原则

​ 这里主要也是分析电梯类,由于采取继承,并且电梯类方法均在父类实现,所以似乎是较为符合ISP的。但是如果有后续的扩展,可能就会出现一些问题。例如增加了一种电梯,拥有其他电梯不具有的属性与方法?可能采用接口会更为合适,可以更好地扩展不同的方法。

3.5DIP——依赖倒置原则

​ 3.1的分析中提到了乘客类与中控类的处理,根据DIP,可能将请求的分派交给中控类/调度类会更好一些,而不是由乘客类进行判断。这样可能能够在未来有更多调度要求的时候,更好地进行扩展。

4.程序结构分析

4.1程序度量分析

Type Name NOF NOPF NOM NOPM LOC WMC NC DIT LCOM FANIN FANOUT
CentralController 5 0 10 9 116 29 0 0 0.4 0 0
Elevator 10 0 6 6 129 25 0 0 0 0 0
ElevatorA 0 0 1 1 5 1 0 0 -1 0 0
ElevatorB 0 0 1 1 5 1 0 0 -1 0 0
ElevatorC 0 0 1 1 5 1 0 0 -1 0 0
InputThread 2 0 2 2 29 5 0 0 0 0 0
MainClass 0 0 1 1 11 1 0 0 -1 0 0
Passenger 5 0 13 13 100 23 0 0 0 0 0
SafeOutput 0 0 1 1 5 1 0 0 -1 0 0

​ 通过DesigniteJava分析,可以看到主要承担任务的是CentralControllerElevatorPassenger三个类。由于职责还是比较集中,可能扩展性不够优良。应当可以考虑将电梯类的部分职责,交给一个调度器类,电梯只根据状态,执行上下行、开关门等具体操作。CentralController类中的需换乘请求拆分,也可以交给另外一个类来处理。同时,设计中可能存在控制类与电梯类耦合过高的情况,这些都是可以优(chong)化(gou)的部分。

4.2UML类图分析

UML类图

​ 与4.1分析相同,通过UML类图很容易可以看出三个主要的类职责过多,应当考虑将职责再细化、分配出去,这样才能实现更好的扩展和模块复用。

4.3Sequence UML

Main

(注:MainClass中还创建了三个初始电梯线程,不知这个工具为何无法显示...)

InputThread Elevator

5.BUG分析

​ 本单元作业,设计的时候考虑安全性第一位,将性能放在次要位置,所以非常幸运地没有在强测及互测中出现bug。

6.hack策略

​ 这单元的hack,我主要采用的仍然是利用自己在测试过程中出现错误的数据点进行hack。如第一次作业中使用了电梯到达某一楼层,接收到该楼层出发、上下行相反的请求,hack到了三位同学。虽然这次比较幸运地在几次互测中都“有所斩获”,但手造数据毕竟有一定的局限性。在将来的互测中应该考虑自动生成数据,并自动评测同学的代码,这样可能会有更多“收获”。

7.心得体会

​ 首先还是想小小地表扬一下自己,在本单元作业中还没有被抓出BUG。上一单元结束以后,助教告诉我“不如花点时间构思然后争取没有大的漏洞”。我也践行了这句话,在这一单元作业中,我确实花了更多时间在构思上。包括考虑用何种设计模式、采取怎样的调度策略,边思考也翻阅一些数据、参考一些文献(虽然最后都没怎么用上)。我认识到了花时间去思考,比敲完代码删掉重写有效率得多,而且少写一遍代码,就少一些出现低级错误的可能。所以,以后也要先思考,再动键盘。

​ 同时在这次的作业中,由于输入需要精确掌握时间,也学习了用python脚本运行命令行,通过重定向的方式对jar进行输入。虽然最后并没有实现自动化测评,但也迈出了自动化测评的第一步?希望下一单元的作业能够试着去实现自动化测评。

​ 在这一单元的作业完成过程中,我也更多地与同学进行交流。我也意识到想要学习更多东西,应该更多地向大佬们请教。比如之前一直无法解决如何用命令行运行有本地jar依赖的java工程,询问了何泽欣同学才知道把项目打包为jar即可。在研讨课上也通过同学的分享收获了很多。所以,多与同学交流,总会有所收获!

posted @ 2020-04-14 20:30  WilliamHuang  阅读(252)  评论(0编辑  收藏  举报