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的网课之中逐渐变得明晰。这个世界本身就是多线程的,瞬息万变。(类比新冠疫情的话,如果其他国家爆发疫情先于中国爆发,世界又是什么样子的呢?)
- 每一次坐电梯时,记得反思自己一秒:当年惨死在自己电梯手里的冤魂可否安好(雾
BUAA_OO_2020_Unit2_总结博客的更多相关文章
- Android请求网络共通类——Hi_博客 Android App 开发笔记
今天 ,来分享一下 ,一个博客App的开发过程,以前也没开发过这种类型App 的经验,求大神们轻点喷. 首先我们要创建一个Andriod 项目 因为要从网络请求数据所以我们先来一个请求网络的共通类. ...
- 一步步开发自己的博客 .NET版(11、Web.config文件的读取和修改)
Web.config的读取 对于Web.config的读取大家都很属性了.平时我们用得比较多的就是appSettings节点下配置.如: 我们对应的代码是: = ConfigurationManage ...
- 一步步开发自己的博客 .NET版(10、前端对话框和消息框的实现)
关于前端对话框.消息框的优秀插件多不胜数.造轮子是为了更好的使用轮子,并不是说自己造的轮子肯定好.所以,这个博客系统基本上都是自己实现的,包括日志记录.响应式布局.评论功能等等一些本可以使用插件的.好 ...
- 【原】Github+Hexo+NextT搭建个人博客
摘要 GitHub 是一个开源项目的托管网站,相信很多人都听过.在上面有很多高质量的项目代码,我们也可以把自己的项目代码托管到GitHub,与朋友们共享交流.GitHub Pages 是Github为 ...
- 我为什么要写LeetCode的博客?
# 增强学习成果 有一个研究成果,在学习中传授他人知识和讨论是最高效的做法,而看书则是最低效的做法(具体研究成果没找到地址).我写LeetCode博客主要目的是增强学习成果.当然,我也想出名,然而不知 ...
- 博客使用BOS上传图片
1.博客平台的选定 从大学开始做个人主页算起,最开始是使用html,CSSS写简单的页面,后面大学毕业之后接触到了WordPress,就开始用WordPress搭建网站.现在还维护着一个农村网站.ht ...
- 在jekyll模板博客中添加网易云模块
最近使用GitHub Pages + Jekyll 搭建了个人博客,作为一名重度音乐患者,博客里面可以不配图,但是不能不配音乐啊. 遂在博客里面引入了网易云模块,这里要感谢网易云的分享机制,对开发者非 ...
- iOS controller解耦探究实现——第一次写博客
大学时曾经做过android的开发,目前的工作是iOS的开发.之前自己记录东西都是通过自己比较喜欢的笔记类的应用记录下了.直到前段时一个哥们拉着我注册了一个博客.现在终于想明白了,博客这个东西受众会稍 ...
- 中文 iOS/Mac 开发博客列表
中文 iOS/Mac 开发博客列表 博客地址 RSS地址 OneV's Den http://onevcat.com/atom.xml 一只魔法师的工坊 http://blog.ibireme.com ...
随机推荐
- Poj1753 翻转棋子
这个题就是用枚举举遍所有情况,然后一个一个深搜看看是不是符合条件,符合条件直接退出,不符合则继续, 由于表格只有16个所以可以得知最多的步数只能是16,所以可以根据步数从0到16依次枚举, 第一个符合 ...
- Keyboard Shortcuts Reference
Sublime Text 3快捷键 Ctrl + Shift + P 打开命令面板 Ctrl + P 搜索项目中的文件 Ctrl + G 跳到第几行 Ctrl + W 关闭当前打开文件 Ctrl + ...
- NO.5 CCS运行demo(云端)
我们在demo的README中发现如果程序在云端运行会有很酷的界面而且功能会多一些. 首先我们在CCS开始界面点击Resourse Explorer 然后在浏览器中找到对应的demo 打开GUI界面, ...
- 强力解决使用node版本管理工具 NVM 出现的问题(找不到 node,或者找不到 npm)
nvm是好用的Nodejs版本管理工具, 通过它可以方便地在本地调换Node版本. 2020-05-28 当前长期稳定版12.17.0,于是 nvm install 12.17.0 然后C:/nvm/ ...
- require.js与IDEA的配合
本文主要讲述在html中使用requirejs时,如何让IDEA更加智能识别javascript的方法. 测试时的目录结构,一种典型的 thinkphp 的结构,同时,在 a.thml 中通过 req ...
- css box-shadow(text-shadow) 阴影学习备忘
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Java实现 LeetCode 493 翻转对
493. 翻转对 给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对. 你需要返回给定数组中的重要翻转对的数 ...
- Java实现【USACO】1.1.2 贪婪的礼物送礼者 Greedy Gift Givers
[USACO]1.1.2 贪婪的礼物送礼者 Greedy Gift Givers 题目描述 对于一群要互送礼物的朋友,你要确定每个人送出的礼物比收到的多多少(and vice versa for th ...
- Java实现 LeetCode 334 递增的三元子序列
334. 递增的三元子序列 给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列. 数学表达式如下: 如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ...
- Java实现 蓝桥杯VIP 算法训练 校门外的树
问题描述 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米.我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置:数轴上的每个整数点,即0,1,2,--,L,都种 ...