BUAA_OO_2020_Unit2_总结博客

BUAA_OO_2020_Unit2_总结

2020年春季学期第八周,OO第二单元落下帷幕,三次多线程任务作罢,萌新在OO的世界里又迈出了艰难但有意义的一步,下作总结:

一、三次作业设计策略

回顾三次电梯作业,整体的设计架构是在第二次的作业时最终确定的,第三次作业沿用了第二次作业的整体思路。总体而言三次作业本人并未重构,而是采用了迭代开发的方式,总体体验甚佳。但仍暴露出一些问题,后面会具体分析。

(1)第一次作业设计策略

  第一次作业相较于17级的三次作业布置,对应为17级的第二次作业。可能考量的是17级的第一次作业相对较为简单,仅能考量正确性而性能分无法评判等因素。所以18级的第一次作业直接晋升为单部可稍带电梯。我三次采用的均为生产者消费者模型,只是随着作业的迭代,消费者与生产者的数量与要求不同。第一次作业时我设立了生产者类为Input,专门读取标准输入的请求信息,转化为PersonRequest后放入托盘——请求队列requestLine中,而消费者——单部电梯Elevator作为电梯与调度器合二为一的体现。内包含了请求队列requestLine与一个进入电梯的请求队列InRequestLine。每当人员进入电梯,则requestLine中的请求会自动转移到InRequestLine中,假若人员到达,出电梯,则InRequestLine中的请求自动删除,完成请求。所以电梯线程停止运行的条件也是,“requestLine空 and InRequestLine空 and 输入停止”。RequestLine作为实现共享对象的类,其设计时遵循多线程设计时的一些原则与技巧。为保证安全性,其实现采用的数据结构我也采用了Java自带的线程安全类CopyAndWriteArrayList,类中的方法也采用了synchronised关键字修饰。而同步控制的设计中,为避免轮询。我采用了requestLine对象上锁的机制。在生产者线程中,读取到PersonRequest时,会用notifyAll唤醒所有wait线程。而在消费者电梯线程时,当判断requestLine与inRequestLine均为空时,此时情景应该是服务都已完成但输入未结束,此时会对requestLine对象wait,等待下一条服务来唤醒。

  而对于Elevator里处理请求的调度算法,我先自己手捏了一个算法,在电梯为空时先接最近楼层的乘客;在不为空时先送电梯中目标楼层最近的乘客。而在经过楼层时,只要有乘客就接进电梯。当时的考量是电梯没有容量限制,手捏的算法也没过多的验证,结果强测的结果十分拉跨;最后还是老老实实改成了LOOK算法。后来在研讨课中学到了贪心算法。在第一次作业中仅考虑总运行时间的情况下我认为贪心算法获得的效果应该优于LOOK算法,因为LOOK算法兼顾了每位乘客的等待时间。在总运行时间上会有一定的牺牲。

(2)第二次作业设计策略

  第二次作业为多部可稍带电梯,而我第一次作业的设计显然无法直接移植。我的考量是这样的,我认为每一部电梯都是一个独立的运行体系。不同的电梯可以有不同的属性,不同的运行策略,不同的运行效果。所以我仍坚持了电梯和运行策略一体的设计。而设计了一个调度器,调度器通过电梯的状态,判断服务的分配问题。首先接收电梯数目,然后在调度器线程中开启对应数目的电梯线程,其中共享的对象为电梯线程里的请求队列。由输入线程-调度器线程构成一级的生产者消费者模式,而调度器-多部电梯线程构成二级的生产者消费者模式。当调度器取到了请求之后,判断是否有当前运行电梯路线上的可稍带条件,(即捎带上次请求)若没有则挑选电梯中请求最少的一部,加入其请求队列中。而电梯的核心算法仍为LOOK算法。

  本次设计遇到的最大问题在强测中暴露无遗,即Scanner线程的安全问题,这个问题在讨论区都有提及,然而由于我的疏忽以及自主测试中并未出现,导致并未对这个bug进行修正,强测给予了致命打击QAQ

(3)第三次作业设计策略

  第三次作业为多部自定义电梯,自定义体现在停靠楼层,限制容量,楼层移动周期。第二次作业的设计策略可以移植于第三次作业之上。需要更改的是,对于电梯自定义属性的定义,以及运行时的约束。对于请求中的变更,我们需要安排换乘策略。针对换乘的需求,在衡定了三种电梯的停靠楼层以及所有可能的请求之后。本人决定采用最多一次换乘的策略。即有必要换乘再换乘。换乘的策略也是针对不同的请求进行不同设计的。由于C类型电梯运行较慢以及覆盖楼层较少,大家对于C电梯的使用比较排斥。而本人认为其运行也是不可或缺的,其可以分担一部分B运行的压力,避免C空闲时间过长导致的整体运行时间增长。我对于电梯分配的策略为:如果出发到达楼层里有3(C独有)则必须交给C,否则判断一下B类电梯的平均请求数,若比C多了1个以上则给B,否则给C。这种考量基于电梯容量,并且希望C类电梯可以运行且不运行过长同时达到分担压力的作用。中转的楼层包括1,5,15。C的运行可以分担一部分1楼,5楼的请求。

  请求经过换乘的分割之后,分割好的请求会进入寻找适合电梯类型的函数之中,返回电梯类型之后,再在所有的符合电梯类型的电梯中找最符合条件的电梯。符合的条件为:分配给最近的电梯,若不则分配给保底:1.电梯为空 2.符合电梯类型的最后一个电梯。找到电梯之后,放入其请求队列即可。每部电梯仍然遵循LOOK算法。而电梯需要增加可停靠楼层的判断,不可停靠的楼层不能开门(在分配请求时已经分配合理的,所以理论上不会在不可停靠楼层开门)。另外在容量为满时也不会上人。加上上述条件之后。电梯运行即可。

  对于增加电梯的请求,只需要在输入线程区分不同的请求,放入不同的队列之中。在调度器线程读取到电梯申请队列不为空时增加线程即可。而线程wait的条件仍为:电梯处理完请求且输入未停止。为避免调度器线程轮询/死锁,在总请求队列为空时我设置了wait(200),这种设计宏观上表现为200ms询问一次,200为电梯运行的最短时间单位,以一个周期运行可以既降低cpu处理的负荷,也可以保证不出现请求处理延误的问题。还可以避免线程出现的死锁,死等问题,也算是我自己使用的一个小trick。

 二、第三次作业可拓展分析

  在第八周的讨论区中,大家也提到了未来可能出现的功能拓展,针对一些特定的功能拓展,比如:动态退出,增加楼梯,乘客优先级,电梯容量更换为重量限制。这些都是体现在电梯设计层面的问题。而对于整个架构而言并不具有特别大的影响,所以在需求提出的时候对电梯属性进行修改即可,下面本人根据SOLID原则对第三次作业进行分析。

  • Single Responsibility Principle:单一职责原则

  在设计之中对于主类,输入类以及请求队列类,调度器类均遵循了单一职责的原则,主类只负责控制线程的执行;输入类负责解析输入放入队列;请求队列负责存储请求,获取请求;调度器类负责分发清秋;唯独电梯类并未遵循单一职责:其负责了电梯的运行以及对于其自身请求队列的调度。在结构优化之后,可以将电梯的运行与调度再度分离;但是由于这种设计在第一次作业中即确定,后续修改会越来越麻烦,且电梯与本身队列的集中调度也有解释的合理性,所以最终也未采用分离的方案,提升了一些耦合度。

  • Open Closed Principle:开闭原则

  这个原则在三次作业的迭代之中体现的较为明显,除了对于三次作业一些调度算法上的修改,每次作业对于特定方法都未修改,而是对需求进行方法的增加,从而面向扩展而不面向修改。

  • Liskov Substitution Principle:里氏替换原则 & Law of Demeter:迪米特法则

  这两条法则在作业中体现较少,由于设计之中只出现了继承Thread类,在线程实现时也是使用的子类方法。其余地方未体现里氏替换原则(因未用继承),对于迭代而言,用子类继承父类不失为一种好实现方式。

  而迪米特法则,在生产者消费者模式中不可避免地会出现中间托盘的相互调用,从而一定程度上提升耦合度。

  • Interface Segregation Principle:接口隔离原则 & Dependence Inversion Principle:依赖倒置原则

  由于本次设计并未采用抽象与接口的实现,所以设计也偏离了这两个原则。在后续改进中可以使类继承自接口或抽象类,类在实现中继承接口,可以避免新增需求时频繁修改多余内容。

三、基于度量的程序结构分析

  三次作业的度量分析与UML类图如下:表格从左到右分别为:ev(G)基本复杂度、Iv(G)模块设计复杂度、v(G)独立路径的条数。

Elevator.arrive() 1.0 1.0 1.0
Elevator.close() 1.0 1.0 1.0
Elevator.Elevator(RequestLine) 1.0 1.0 1.0
Elevator.in(int) 1.0 1.0 1.0
Elevator.judgeDirection() 13.0 15.0 22.0
Elevator.judgeOpen() 5.0 3.0 5.0
Elevator.move() 1.0 1.0 3.0
Elevator.open() 1.0 1.0 1.0
Elevator.out(int) 1.0 1.0 1.0
Elevator.run() 3.0 13.0 14.0
Input.Input(RequestLine) 1.0 1.0 1.0
Input.run() 3.0 4.0 4.0
MainClass.main(String[]) 1.0 1.0 1.0
RequestLine.get() 1.0 3.0 3.0
RequestLine.get(PersonRequest) 1.0 3.0 3.0
RequestLine.getInputEnd() 1.0 1.0 1.0
RequestLine.getRequests() 1.0 1.0 1.0
RequestLine.isEmpty() 1.0 1.0 1.0
RequestLine.put(PersonRequest) 1.0 1.0 1.0
RequestLine.remove(PersonRequest) 1.0 1.0 1.0
RequestLine.RequestLine() 1.0 1.0 1.0
RequestLine.setInputEnd() 1.0 1.0 1.0
RequestLine.size() 1.0 1.0 1.0
Total 43.0 58.0 70.0
Average 1.8695652173913044 2.5217391304347827 3.0434782608695654

第一次作业度量分析

Controller.Controller(RequestLine,int) 1.0 2.0 2.0
Controller.dispense(PersonRequest) 2.0 9.0 9.0
Controller.run() 3.0 12.0 12.0
Elevator.arrive() 1.0 1.0 1.0
Elevator.close() 1.0 1.0 1.0
Elevator.Elevator(RequestLine,String) 1.0 1.0 1.0
Elevator.Elevator(String) 1.0 1.0 1.0
Elevator.getDirection() 1.0 1.0 1.0
Elevator.getNumOfPeople() 1.0 1.0 1.0
Elevator.getStorey() 1.0 1.0 1.0
Elevator.in(int) 1.0 1.0 1.0
Elevator.judgeDirection() 15.0 12.0 22.0
Elevator.judgeOpen() 5.0 3.0 5.0
Elevator.move() 1.0 1.0 5.0
Elevator.open() 1.0 1.0 1.0
Elevator.out(int) 1.0 1.0 1.0
Elevator.run() 6.0 17.0 19.0
Input.Input(RequestLine,ElevatorInput) 1.0 1.0 1.0
Input.run() 3.0 4.0 4.0
MainClass.main(String[]) 1.0 1.0 1.0
RequestLine.get() 1.0 3.0 3.0
RequestLine.get(PersonRequest) 1.0 3.0 3.0
RequestLine.getInputEnd() 1.0 1.0 1.0
RequestLine.getRequests() 1.0 1.0 1.0
RequestLine.isEmpty() 1.0 1.0 1.0
RequestLine.put(PersonRequest) 1.0 1.0 1.0
RequestLine.remove(PersonRequest) 1.0 1.0 1.0
RequestLine.RequestLine() 1.0 1.0 1.0
RequestLine.setInputEnd() 1.0 1.0 1.0
RequestLine.size() 1.0 1.0 1.0
Total 58.0 86.0 104.0
Average 1.9333333333333333 2.8666666666666667 3.466666666666667

第二次作业度量分析

Controller.addElevator() 1.0 1.0 1.0
Controller.Controller(RequestLine) 1.0 2.0 2.0
Controller.findElevator(String,PersonRequest) 5.0 9.0 11.0
Controller.findType(PersonRequest) 6.0 8.0 10.0
Controller.getTypeEleNum(String) 1.0 2.0 3.0
Controller.getTypeEleSumReq(String) 1.0 3.0 3.0
Controller.handle(PersonRequest) 11.0 15.0 18.0
Controller.run() 7.0 16.0 17.0
Elevator.arrive() 1.0 1.0 1.0
Elevator.close() 1.0 1.0 1.0
Elevator.contains(int) 5.0 3.0 5.0
Elevator.Elevator(RequestLine,RequestLine,String,String) 2.0 2.0 5.0
Elevator.getDirection() 1.0 1.0 1.0
Elevator.getNumOfPeople() 1.0 1.0 1.0
Elevator.getStorey() 1.0 1.0 1.0
Elevator.getType() 1.0 1.0 1.0
Elevator.in(int) 1.0 1.0 1.0
Elevator.isFree() 1.0 2.0 2.0
Elevator.isFull() 1.0 1.0 1.0
Elevator.judgeDirection() 15.0 13.0 23.0
Elevator.judgeOpen() 6.0 3.0 6.0
Elevator.move() 1.0 1.0 5.0
Elevator.open() 1.0 1.0 1.0
Elevator.out(int) 1.0 1.0 1.0
Elevator.run() 6.0 17.0 19.0
Input.Input(RequestLine) 1.0 1.0 1.0
Input.run() 3.0 6.0 6.0
MainClass.main(String[]) 1.0 1.0 1.0
RequestLine.getElevatorRequest() 1.0 3.0 3.0
RequestLine.getElevatorRequests() 1.0 1.0 1.0
RequestLine.getInputEnd() 1.0 1.0 1.0
RequestLine.getPersonRequest() 1.0 3.0 3.0
RequestLine.getPersonRequest(PersonRequest) 1.0 3.0 3.0
RequestLine.getPersonRequests() 1.0 1.0 1.0
RequestLine.isEmptyOfER() 1.0 1.0 1.0
RequestLine.isEmptyOfPR() 1.0 1.0 1.0
RequestLine.put(ElevatorRequest) 1.0 1.0 1.0
RequestLine.put(PersonRequest) 1.0 1.0 1.0
RequestLine.remove(ElevatorRequest) 1.0 1.0 1.0
RequestLine.remove(PersonRequest) 1.0 1.0 1.0
RequestLine.RequestLine() 1.0 1.0 1.0
RequestLine.setInputEnd() 1.0 1.0 1.0
RequestLine.sizeofER() 1.0 1.0 1.0
RequestLine.sizeofPR() 1.0 1.0 1.0
Total 100.0 137.0 170.0
Average 2.272727272727273 3.1136363636363638 3.8636363636363638

第三次作业度量分析

第一次作业UML

第二次作业UML

 第三次作业UML

 

UML时序图

  从上述直观分析来看,三次作业的复杂度集中在了Elevator类中的judgeDirection,Controller类中的分发请求方法。这些方法涉及对电梯状态的访问,从而增加了耦合度。如果可以将电梯与电梯本身的请求队列分开,可以一定程度上的解决问题。

四、程序bug分析

  在前两次作业中,强测出现了翻车的情况;第三次作业总体并未发现bug,获得分数也较为理想。可能是前两次把雷都踩过之后第三次更小心处理的结果。

  第一次作业中:由于调度算法的低效,程序出现了超过ALS运行时间的bug,在修改为LOOK算法之后得到修复

  第二次作业中:遇到了scanner本身线程安全问题的bug,原因为在不同线程开启了同一个scanner导致输入线程不安全导致第一个乘客请求无法回复。这个bug在将输入scanner统一即可解决问题。这个bug应当在今后的OO编程中警钟长鸣。

五、发现bug策略

  第二单元的作业本人第一次全自主利用python与java构造了评测机。利用java手写的judge模块让我对错误可能出现的地方更加深刻。而python popen模块的学习与使用也让我解决了评测的问题,(定点投入测试数据,收回测试数据,数据重定向等问题)搭建评测机的过程是对自己本身能力的一种提升,也是对自己考虑问题全面性的一个全方位考量。利用评测机,我发现了自己的bug,也跑出了别人程序的问题。但是不可避免的运行时间过长导致的低效问题需要更好的策略来解决。

六、心得体会

  • 多线程的概念在OS和OO的课程中交相辉映,在一次次OO的作业之中,一堂堂OS的网课之中逐渐变得明晰。这个世界本身就是多线程的,瞬息万变。(类比新冠疫情的话,如果其他国家爆发疫情先于中国爆发,世界又是什么样子的呢?)
  • 每一次坐电梯时,记得反思自己一秒:当年惨死在自己电梯手里的冤魂可否安好(雾

 

原文地址:https://www.cnblogs.com/Craaaaazy/p/12704595.html