BUAA面向对象设计与构造——第二单元总结
BUAA面向对象设计与构造——第二单元总结
第一阶段:单部傻瓜电梯的调度
第二阶段:单部可捎带电梯的调度
(由于我第一次写的作业就是可捎带模式,第二次只是增加了负数楼层,修改了一部分参数,因此一起总结。)
1.设计策略
这次作业我设计了两个线程:Elevator和DealWithInput,前者模拟电梯,而后者用来处理输入。队列PersonQueue作为一个单例模式下的对象,被两个线程所共有,充当调度器的角色。
DealWithInput基本套用下发的模板,只是增加了每次读到新请求就通知调度器pq.add(requst),并且pq.notifyall(),以用来唤醒电梯。在读到EOF后,设置标志输入结束的flag,并再次唤醒电梯,防止电梯一直等待。
PersonQueue调度器里包含了一个Floor类型的数组,将每个楼层独立处理。每个Floor类中包含一个记录等待乘客的数组waitingPersion,乘客按到来的先后顺序进入数组。描述乘客的Person类记录了每个乘客的ID、起始楼层、目的楼层和运行方向。电梯向调度器询问主请求时,调度器扫描所有楼层,查看是否为空,只要有一个楼层不为空,电梯就仍需运行。当到达某一层后,电梯需要捎带,这时又要向调度器发出询问。调度器将这个询问转交给对应的Floor对象。Floor遍历本楼层全部乘客,如果有相同方向的乘客,则把这些乘客打包成一个数组返回给调度器,并且从该楼层的等待队列删去。之后调度器再返回给电梯。
Elevator采用扫描算法,即当电梯静止时,先向调度器询问主请求,根据主请求决定运行方向,之后到达每一层如果还有同方向的乘客则捎带。下乘客则是由电梯自己判断,电梯包含了一个Person类型的eleList数组,记录电梯里的人。每到一层,先下后上,电梯遍历数组查看是否有人的目的楼层是当前层,如果有,则让他下电梯并且从电梯数组中删除。上下乘客结束后,电梯要向调度器询问是否需要改变方向。改变方向后进行二次上乘客。这个操作是针对这种情况:例如电梯向上运行到达5层,所以第一次查找乘客时只会查找5层要向上的乘客。但是如果这层没有人向上,电梯内已经没人了,5层以上的楼层也没有需求,因此电梯可以改变方向下,并且接5层向下的乘客。并且二次查找乘客也可以尽量多的捎带。
时序图如下:
第五次和第六次作业的一个比较大的区别在于:
第五次作业电梯采用轮询方式,比较占用CPU。
第六次作业改成wait()和notifyall()的模式,如果楼里没有请求,那么电梯会wait,而当输入进程读到了新的请求时会唤醒电梯进程。当读到EOF时,也需要唤醒电梯防止一直等待。
2.结构度量
类图如下:
类图展现了类之间的实例化和创建关系。主进程MainClass创建了Elevator和DealWithInput两个进程,DealWithInput直接向PersonQueue增加请求,PersonQueue管理多个Floor类,每个Floor管理等待的Person.Elevator进程和调度器互相传递信息,同时也管理Person来记录电梯内部的人。
以上是关于程序本身的一些数据统计。可以看到每个类的平均循环复杂度还是比较低的,说明方法的设计比较符合“高内聚、低耦合”的原则,而不是大多数功能集中在某一个方法中。
3.程序bug
第五次作业在公测中没有什么bug,但是用相同的架构在第六次的弱测就发现了bug。
首先最致命的bug是程序CPU运行时间超时。因为我第五次作业采用轮询模式,电梯一直在while循环,每次循环都询问调度器是否需要运行。这种模式在第五次比较简单的测试中侥幸存活了下来,但是第六次就不行了。因此我改为了等待与唤醒的模式。电梯如果询问调度器得到的回答是暂时不需要运行,那么将调用pq.wait()进行等待。而输入进程一旦获取到新的需求,就调用pq.notifyall()来唤醒电梯。
其次是电梯的方向问题。第五次作业我的电梯是送完乘客且运行方向的楼层没有乘客了,才进行方向的改变。但是这样有可能发生在某一层第一次开门下乘客,然后电梯意识到要改变方向,再次开门上乘客。因此为了节省时间以及更加符合实际,我在每次开关门之间增加了对运行方向的判断,修改为二次搭乘模式。
第三阶段:多部可捎带电梯的调度
1.设计策略
这次我依然选择将调度器作为一个共享对象,而不是进程的模式。输入进程依旧存在,电梯进程变成了三个,电梯单独运行的框架和前两次类似,也都是先向调度器询问主请求(即启动方向),每到一层都询问可捎带乘客和该下电梯的乘客。
第七次作业比较大的一个改进在于赋予了Person类更多的功能。就像老师上课讲的工厂和工人的例子一样,乘客可以自己判断上下电梯,并且在一开始就规划换乘的路线。每当实例化一个Person对象时,将调用内部方法,来判断该乘客是否需要换乘。如果不需换乘,那么就当作正常的乘客,记录其起始楼层和目的楼层。如果需要换乘,那么将调用方法来得到他的换乘楼层(middleFloor),并且将这位乘客的目的楼层存入换乘楼层。当该乘客在目的楼层(即换乘楼层)下电梯后,再修改自身的起始楼层为换乘楼层,目的楼层为真正的目的楼层,将更新后的这名乘客再次加入等待队列。这样相当于把要换乘的乘客分为两名乘客,先后加入等待队列。这样就把换乘这一难题交付给乘客来完成,调度器辅助更新,而电梯只需要正常运行,更加清晰有效地分配了功能。因此IN和OUT这两个方法都在Person类,而不是电梯进程来完成。
按照题目中电梯楼层的分配,有一些乘客是可以乘坐多个电梯都能到达的。例如ABC三部电梯都可以从1层到15层。因此我先在Person类中加入了可乘坐的电梯这一属性,用三位二进制数表示是否能乘坐对应电梯,例如其值为111表示三部电梯都可乘坐。这一属性是在判断换乘时同时完成的,而对于要换乘的人,在他第一次OUT后更新楼层信息时,同时更新这个属性。
为了实现三部电梯的配合,我又引入了按钮属性。每个Floor类增加了三个数组,每个数组有两个元素,这六个值描述了ABC三部电梯上下行共六个按钮。当乘客第一次加入某一楼层等待队列时,考虑以下三种情况:
1.只能坐一部电梯,那么就按对应电梯对应方向的按钮;
2.可以坐两部电梯AB,那么若A到达时间短/A排队人数没有满员/B的反方向按钮被按则按A电梯,否则按B电梯。关于B的反方向按钮被按这一点,主要是因为如果B电梯要先以反方向经过本楼层的话,那么同方向会等更久。因此如果A的理论到达时间更短就优先选A。
3.可以坐三部电梯,那么类似于第2种情况,从三部中选一部按按钮。
因此每个乘客在刚进入等待队列时,都只按了一个理论上最合适的电梯。当然这只是理论值,不能排除电梯因为停靠楼层开关门所导致的时间延误。因此当电梯A(BC同理)每运行一层时,还要通知调度器刷新按钮。对于楼层x,如果此时A刚从x层上行,那么我们可以推测对于A再次以下行方向返回x层需要比较久的时间,因此对于低于x层的楼层,如果等待A电梯的人势必要等很久。因此这时刷新所有<=x层的楼层,如果等待A电梯的乘客中,有可以乘坐BC电梯的人,那么也按BC的按钮,这样如果BC电梯先于A电梯到达,那么这些人就可以早一步上电梯。
这样一来,每个人虽然最开始只按了一个按钮,但可能因为刷新操作按多个。因此当电梯停靠一层并进行了上下乘客操作后,要遍历该楼层的所有乘客再次进行按电梯操作。这里的按电梯原则与最开始是一样的,都只按一个。按多个的刷新是在电梯运行到其他楼层时才进行。
通过按钮这个属性,使得电梯之间的调度更加充分,而不是只集中在一部电梯上。每部电梯在向调度器询问时,调度器遍历每一楼层的按钮即可安排电梯接下来的运行方向了。
2.结构度量
类图如下:
类图展现了类之间的实例化和创建关系。主进程MainClass创建了Elevator和DealWithInput两个进程,DealWithInput直接向PersonQueue增加请求,PersonQueue管理多个Floor类,每个Floor管理等待的Person.Elevator进程和调度器互相传递信息,同时也管理Person来记录电梯内部的人。Person类拥有了更多的功能,诸如向调度器报告按按钮,在电梯进程中进行IN和OUT的操作等。
这一次作业Floor类和Person类的复杂度增长幅度很大,主要是因为赋予了Person更多的功能,以及Floor添加了按钮属性所导致的。但总体来看复杂度还算比较平均,没有出现某一类包揽大多数功能的情况。
3.程序bug
公测中全部通过,但性能分不太高。我认为是因为我的电梯调度还是针对于一些细节部分的优化,例如尽量多按按钮来使得乘客尽早上电梯。但是这也可能导致电梯空跑。我觉得一个绝对的优化策略还是很难找(几乎不存在)的,因为电梯对即将到来的乘客是未知的。因此优化这一点只能尽力从架构上设计地更完备,考虑更多的情况。
在弱测和本地测试中,我发现我还是遇到了CPU超时的情况。虽然单部不会忙等,但是由于多部电梯同时工作,才会导致这一错误。当AB电梯都没有需求后,他们发现输入也结束了,但是楼里还有人,因此会进入等待状态,此时C还在工作,当C结束工作后,他检测到输入也早早结束了,因此结束了进程。但是此时AB没有被唤醒,也就无法检测楼里没人这个真正的结束条件。另一种情况是最后一个乘客需要换乘,他从C电梯出来后,AB电梯还在等待,也就没有电梯来接他了。因此我对于电梯在每一层的运行结束后都加入了notifyall,这可以解决以上两个问题,并且对于不需要工作的电梯,即使被唤醒后也能马上再次进入等待。
总结
根据SOLID原则分析:
SRP(单一责任原则):我一共设计了MainClass, DealWithInput, Elevator, PersonQueue, Floor, Person六个类,前三个类分别是三个进程,第四个作为调度器,后两个分别针对楼层和乘客两个角度设计功能。实现了功能的划分,使得程序架构条理清晰。
OCP(开放封闭原则):指架构应该是可扩展的。这个单元我做的比较好的一点就在于减少了代码的重构,第六次和第五次代码几乎一样,只是针对bug和指导书不同的要求进行一小部分的修改。第七次作业里,每部电梯的运行架构也和第六次一样,只是增加了电梯间配合调度的功能。
由于本次作业没有使用继承、接口和抽象类,因此其他三个原则不做讨论。
对于线程安全方面,我采用synchronized加锁的模式。所有针对调度器即等待队列的操作都需要加锁,包括添加乘客,捎带乘客(即取走乘客)和判断是否为空等。这样才能保证对象的安全,防止出现一个乘客同时上两部电梯,或者是从队列中删除但是却没有上电梯的错误。
总之经过这一单元的练习,增强了我对多线程的认识,对于线程安全有了更深刻的体会。不再像第一单元那样每次重构代码,是本单元最大的进步。
BUAA面向对象设计与构造——第二单元总结的更多相关文章
- BUAA面向对象设计与构造——第一单元总结
BUAA面向对象设计与构造——第一单元总结 第一阶段:只支持一元多项式的表达式求导 1. 程序结构 由于是第一次接触面向对象的编程,加之题目要求不算复杂,我在第一次作业中并没有很好利用面向对象的特点, ...
- 面向对象设计与构造:JML规格单元作业总结
面向对象设计与构造:JML规格单元作业总结 第一部分:JML语言理论基础 JML语言是什么:对Java程序进行规格化设计的一种表示语言 使用JML语言有什么好处: 用逻辑严格的规格取代自然语言,照顾马 ...
- 面向对象设计与构造:oo课程总结
面向对象设计与构造:OO课程总结 第一部分:UML单元架构设计 第一次作业 UML图 MyUmlInteraction类实现接口方法,ClassUnit和InterfaceUnit管理UML图中的类和 ...
- 从结构和数字看OO——面向对象设计与构造第一章总结
不知不觉中,我已经接触OO五周了,顺利地完成了第一章节的学习,回顾三次编程作业,惊喜于自身在设计思路和编程习惯已有了一定的改变,下面我将从度量分析.自身Bug.互测和设计模式四个方向对自己第一章的学习 ...
- BUAA OO 2019 第二单元作业总结
目录 总 架构 controller model view 优化算法 Look 算法 多种算法取优 预测未来 多线程 第五次作业 第六次作业 第七次作业 代码静态分析 UML 类图 类复杂度 类总代码 ...
- 2020-BUAA-OO-面向对象设计与构造-第四单元总结&课程总结
咱的OO结束辣! Part1: Unit4 Summary 本单元作业,我主要使用了适配器模式和访问者模式.总体上看,代码量和文件数量有所上升,但配合分包等措施后,文件结构清晰,各部分耦合度均较低.缺 ...
- 「BUAA OO Unit 2 HW8」第二单元总结
「BUAA OO Unit 2 HW8」第二单元总结 目录 「BUAA OO Unit 2 HW8」第二单元总结 Part 0 前言 Part 1 第五次作业 1.1 作业要求 1.2 架构设计 1. ...
- 第二单元总结:基于synchronize锁的简单多线程设计
单元统一的多线程设计策略 类的设计 电梯 每部电梯为一个线程. 电梯从调度器接收原子指令,知晓自己的状态(内部的人/服务的人.运行方向.所在楼层) 原子指令包括且仅包括: 向上走一层 / 向下走一层 ...
- 面向对象设计(Object-Oriented Design,OOD)
前言 OOD简介 Shubho:亲爱的,让我们开始学习OOD吧.你了解面向对象原则吗? Farhana:你是说封装,继承,多态对吗?我知道的. Shubho:好,我希望你已了解如何使用类和对象.今天我 ...
随机推荐
- 关于 Microsoft Dynamics CRM has encountered an error 弹窗的问题
最近用 IE 测试 CRM 网站的时候发现一个问题:时不时会弹出“Microsoft Dynamics CRM has encountered an error”的小框框,而且还不是在特定位置才会弹出 ...
- 基于IPV6的数据包分析(GNS3)
1.拓扑图 2.配置ipv6地址.使路由器之间可互ping,用ospf配置.(R5为例) 查看路由表 试R5 ping 到R4 R4 ping到 R1 3.开始抓包分析 128返回请求(Echo Re ...
- Storage 001 电商数据库设计
[大概流程 ]用户登录 > 选购商品 > 加入购物车 > 检查库存 >提交订单 > 选择在线支付 或 选择货到付款 > 发货 [用户模块]注册 登陆 [商 ...
- Studio 5000 指针(间接寻址)编程
前言:自动化控制系统是综合性.复杂性的,处于现场层的PLC控制器虽然进行各种控制,但最终还是对数据进行处理,对数据的处理,包含两种方式,一种是直接使用,第二种就是间接使用.针对第二种处理方式,就要用 ...
- HTML和XHTML区别
HTML和XHTML 可扩展超文本标记语言XHTML(eXtensible HyperText Markup Language)是将超文本标记语言HTML(HyperText Markup Langu ...
- JMX - JMX定义
JMX定义 JMX超详细解读 https://www.cnblogs.com/dongguacai/p/5900507.html 开源框架是如何通过JMX来做监控的(一) - JMX简介和Standa ...
- Revit二次开发封装族的替代方法
使用草图平面:
- loj题目总览
--DavidJing提供技术支持 现将今年7月份之前必须刷完的题目列举 完成度[23/34] [178/250] 第 1 章 贪心算法 √ [11/11] #10000 「一本通 1.1 例 1」活 ...
- windows 下 配置 github
github 功能介绍 1. 记录多个版本 2.查看历史操作,可以进行版本回退和前进的控制 3. 多端共享代码,自动合成 Github 与 SVN 1. SVN 版本集中管理,所 ...
- shutil&shelve
https://www.cnblogs.com/xiangsikai/p/7787101.html http://www.cnblogs.com/wupeiqi/articles/4963027.ht ...