OO电梯单元作业总结

2019年OO电梯单元作业总结

多线程的协同与控制

电梯单元的作业是笔者在多线程程序设计上的第一次尝试,从最基础的生产者消费者模型中,我体会到线程安全的重要性,如果没有线程安全和同步机制做保证,程序的运行一般是无法得到正确结果的。

谈到线程安全,就不得不提到需要确保安全的对象——请求队列,它相当于生产者消费者模型中的托盘,由输入线程不断地向其中添加指令,电梯在请求队列不为空的时候取出指令并执行。在本单元的作业中,为了保证调度器对象的线程安全和同步机制,我主要采用了synchronize关键词和wait/notify机制。对请求多列中所有访问属性以及改变属性的方法加锁,以确保线程安全(虽然这样会带来性能的降低),以及电梯在“无法取指令且输入未结束”时陷入等待,一旦请求队列添加指令,便唤醒电梯。

基于度量的程序分析

第一次作业——傻瓜电梯

设计和策略:

傻瓜电梯按照“先来先服务”的原则进行调度,所以调度策略十分简单:调度器中维护一个请求队列,输入线程不断向其中添加指令,电梯线程则不断取出进行执行。在此生产者消费者的模型中,使用了wait和notify机制来避免轮询可能导致的CPU时间超标。

利用枚举变量表示电梯的上下方向和人(请求)的上下方向。

在这次设计中,调度器作为电梯的“大脑”,控制着电梯的行为,并且不断与请求队列进行交互取指令,电梯类只负责开关门和移动动作。

代码度量分析:

UML生成的类图如下:

 

使用DesigniteJava生成的代码分析报告:

method nameevivv
myelevator.Dispatcher.Dispatcher()1.01.01.0
myelevator.Dispatcher.elevatorPick()1.04.04.0
myelevator.Dispatcher.elevatorSend()1.04.04.0
myelevator.Dispatcher.getSingleReq()1.01.01.0
myelevator.Dispatcher.manMove(int,OnOff,int)1.03.03.0
myelevator.Dispatcher.run()3.04.05.0
Total8.017.018.0
Average1.33333333333333332.83333333333333353.0

通过代码分析可以看出,部分类内的方法过于臃肿,比如调度器中的run(), Send(), Pick(),在后来的两次作业中,我对这些较大的方法进行了拆分,分解为较小的方法以降低圈复杂度。

公测:
遇到了很奇怪的问题,交作业的时候中测全过,但是没进互测,之后发现强测分也100;最后助教说是中测有个点没过??
总结:

这是笔者第一次上手多线程设计程序,通过对典型模型“生产者-消费者”模型的理解,将更复杂的需求用该模型来实现。在这次作业中,我体会到并发程序运行的高效性,而且多线程设计是一种更贴近生活的设计模式,设想在现实生活中,向电梯发送请求是源源不断且时间不定的,不可能存在一部这样的电梯:等多有指令输入完成之后才开始运行。因此,这次电梯作业将我们的程序引入了生活之中,突破了以往“用程序解题”的局限,相信也给同学们带来了不错的成就体验。

不足之处:

程序结构依然存在高内聚的问题,再加上这是第一次设计真正的多线程工程代码,太多的时间花在了如何让程序没有bug上,于是代码风格还是较差

debug的时候,仍然采用传统的IDEA debuger 进行调试,这种调试在单线程开发中确实非常实用,但是到了多线程中,由于JVM调度的不确定性,有时候很难定位bug的位置;所以笔者后来采用了学长封装的DebuggerHelper进行定位输出调试,效率提高了许多。

第二次作业——捎带电梯

设计思路:

电梯处于空闲状态的时候,从队列中取出一条电梯最快到达的指令先运行起来;

电梯处于运行状态时:每到一层楼,判断是否有乘客需要上下;如果有乘客需要下电梯,那么,需要在该层出电梯的乘客先下;如果有乘客需要上电梯,那么在关门sleep的时间之后,从请求队列中取出所有能在这一层上电梯的乘客。

调度器判断电梯是否能捎带的条件为:

direction == passenger.getDirection() &&  curFloor == passenger.getSrcFloor())

Project NamePackage NameType NameMethodNameLOCCCPC
O5alselevator.liftElevatorElevator510
O5alselevator.liftElevatorElevator921
O5alselevator.liftElevatorElevator921
O5alselevator.liftElevatorElevator910
O5alselevator.liftElevatorElevator910
O5alselevator.liftElevatorElevator310
O5alselevator.liftElevatorElevator310
O5alselevator.liftElevatorElevator311
O5alselevator.liftElevatorConstElevatorConst011
O5alselevator.liftElevatorConstElevatorConst011
O5alselevator.liftElevatorConstElevatorConst010
O5alselevator.liftElevatorConstElevatorConst010
O5alselevator.liftFloorFloor411
O5alselevator.liftFloorFloor310
O5alselevator.liftFloorFloor310
O5alselevator.liftFloorFloor311
O5alselevator.liftFloorFloor311
O5alselevator.liftLiftControllerLiftController822
O5alselevator.liftLiftControllerLiftController611
O5alselevator.liftLiftControllerLiftController4180
O5alselevator.liftLiftControllerLiftController820
O5alselevator.liftLiftControllerLiftController1770
O5alselevator.liftLiftControllerLiftController1771
O5alselevator.liftLiftControllerLiftController1440
O5alselevator.liftLiftControllerLiftController831
O5alselevator.liftLiftControllerLiftController53100
O5alselevator.liftLiftControllerLiftController3071
O5alselevator.liftLiftControllerLiftController1941
O5alselevator.liftPassengerPassenger1221
O5alselevator.liftPassengerPassenger310
O5alselevator.liftPassengerPassenger310
O5alselevator.liftPassengerPassenger310
O5alselevator.liftPassengerPassenger310
O5alselevator.liftPassengerPassenger310
O5alselevator.liftPassengerPassenger311
O5alselevator.requestInputHandleInputHandle2531
O5alselevator.requestReqListReqList410
O5alselevator.requestReqListReqList411
O5alselevator.requestReqListReqList1430
O5alselevator.requestReqListReqList310
O5alselevator.requestReqListReqList411
O5alselevator.requestReqListReqList310
O5alselevator.requestReqListReqList311
O5alselevator.requestSingleRequestSingleRequest821
O5alselevator.requestSingleRequestSingleRequest310
O5alselevator.requestSingleRequestSingleRequest310
O5alselevator.requestSingleRequestSingleRequest310
O5alselevator.requestSingleRequestSingleRequest310
O5alselevator.schedulerSchedulerScheduler411
O5alselevator.schedulerSchedulerScheduler3082
O5alselevator.schedulerSchedulerScheduler1542
O5alselevator.schedulerSchedulerScheduler310
O5alselevator.schedulerSchedulerScheduler310

针对复杂度较高的电梯控制器进行了进一步的代码分析发现如下问题:

部分方法耦合度仍然较高,比如图表中标红的方法:getPassenger(),在这个方法中,我进行了大量的if-else判断,所以导致圈复杂度和模块设计复杂度都较高,但是基本复杂度较低;经过反思,我逐渐意识到“高内聚,低耦合”的重要性,我在此方法中进行了多于一件工作的处理,这不符合“开闭原则”,在之后优化代码的工作中,我设想把这个“大方法”拆分为几个较小的方法来协作完成一项任务。

贴出部分代码进一步分析:

在“添加乘客”这个方法中,我不仅完成了将乘客添加到电梯中,而且为电梯设置了方向,还重新调用了方法本身……这一系列的不合理设计,在当时竟然都没有察觉出来,但是听取老师课上对OO多线程设计模式的分析之后,我对方法设计有了新的认识。

public int addPassenger() {
ArrayList<Passenger> pasList = scheduler.getPasList();
if (elevator.getDirection() == Direction.Wait && !pasList.isEmpty()) {
passengers.addAll(pasList);
if (elevator.getCurFloor() == pasList.get(0).getSrcFloor()) {
if (elevator.getCurFloor() < pasList.get(0).getDstFloor()) {
elevator.setDirection(Direction.Up);
} else if (elevator.getCurFloor()
> pasList.get(0).getDstFloor()) {
elevator.setDirection(Direction.Down);
}
floors.add(new Floor(pasList.get(0).getDstFloor()));
floors.add(new Floor(pasList.get(0).getSrcFloor()));
} else if (elevator.getCurFloor() < pasList.get(0).getSrcFloor()) {
elevator.setDirection(Direction.Up);
floors.add(new Floor(pasList.get(0).getSrcFloor()));
floors.add(new Floor(pasList.get(0).getDstFloor()));
} else {
elevator.setDirection(Direction.Down);
floors.add(new Floor(pasList.get(0).getSrcFloor()));
floors.add(new Floor(pasList.get(0).getDstFloor()));
}
pasList.remove(0);
scheduler.update(elevator.getDirection(), elevator.getCurFloor());
addPassenger();
}
分析自己的bug

在自测中,我主要针对程序较为薄弱的环节进行测试,主要有以下几个方面:

电梯的转向、电梯从N楼处的启动、电梯的捎带

电梯的转向即:当电梯到达最高层以后,或者没有向上的请求,则需要转向。

例如给出请求:在执行完3的请求之后,电梯应该要调转方向,我认为这是程序中一个较为薄弱的环节。

1-FROM-2-TO-12
2-FROM-2-TO-13
3-FROM-2-TO-14
4-FROM-12-TO-2

电梯从N(N != 1)楼启动是指:当运送乘客到某一楼层但输入未结束时,电梯将进入等待状态;在测试中,这也是一个bug易发的环节。

[0.0]1-FROM-2-TO-12
[10.0]2-FROM-12-TO-16
公测:

我方:得分86-87之间,由于没有对性能进行优化,所以只拿到了基础的正确分数,强测没有错误点。

互测:

采用传统的“读代码”方式debug,效率极低,而且几乎无法发现同组成员的bug,所以,提交的几次数据均未成功hack到对方。

第三次作业——多部捎带电梯

设计思路:

在第二次作业的基础之上,限制了电梯的楼层,所以我将1,15楼作为中转楼层,即乘客到达1,15楼时,如果目标楼层不在当前搭乘的电梯中,则需要换乘,换乘时,构造一条新的指令加入到请求队列中。然后限制电梯能捎带指令的楼层问题就转化为第二次电梯作业的模型了.

用UML和代码分析工具生成的类图如下:

method nameev(G)iv(G)v(G)
elevator.ElevatorBase.arrive()1.03.03.0
elevator.ElevatorBase.checkArrive(Passenger)1.09.09.0
elevator.ElevatorBase.checkDir()3.04.05.0
elevator.ElevatorBase.checkOff()3.02.03.0
elevator.ElevatorBase.checkTrans()4.04.05.0
elevator.ElevatorBase.close(int)1.02.02.0
elevator.ElevatorBase.ElevatorBase(double,int,Scheduler,LiftType)1.01.01.0
elevator.ElevatorBase.getCurFloor()1.01.01.0
elevator.ElevatorBase.getDstFloor()1.01.01.0
elevator.ElevatorBase.getMainPerson()1.01.01.0
elevator.ElevatorBase.getOff()1.08.09.0
elevator.ElevatorBase.getOn(Passenger)1.01.01.0
elevator.ElevatorBase.getScheduler()1.01.01.0
elevator.ElevatorBase.getStatus()1.01.01.0
elevator.ElevatorBase.getTransFloors()1.01.01.0
elevator.ElevatorBase.getWaitList()1.01.01.0
elevator.ElevatorBase.isFull()1.01.01.0
elevator.ElevatorBase.liftMove()1.03.07.0
elevator.ElevatorBase.open(int)1.01.01.0
elevator.ElevatorBase.pickPerson()4.05.06.0
elevator.ElevatorBase.run()3.03.04.0
elevator.ElevatorBase.sendPerson()2.04.05.0
elevator.ElevatorBase.setDirection()1.04.07.0
elevator.ElevatorBase.setDoor(Passenger)1.06.08.0
elevator.ElevatorBase.setDstFloor(int)1.01.01.0
elevator.ElevatorBase.setFloors(List)1.01.01.0
elevator.ElevatorBase.setMainPerson(Passenger)1.01.01.0
elevator.ElevatorBase.setStatus(LiftStatus)1.01.01.0
elevator.ElevatorBase.upDateDst(Passenger)1.03.03.0
elevator.LiftA.LiftA(double,int,Scheduler,LiftType)1.01.01.0
elevator.LiftB.LiftB(double,int,Scheduler,LiftType)1.01.01.0
elevator.LiftB.upDateDst(Passenger)1.04.04.0
elevator.LiftC.arrive()1.07.07.0
elevator.LiftC.LiftC(double,int,Scheduler,LiftType)1.01.01.0
elevator.LiftC.upDateDst(Passenger)2.07.07.0
elevator.Passenger.getDirection()1.01.01.0
elevator.Passenger.getFromFloor()1.01.01.0
elevator.Passenger.getPersonId()1.01.01.0
elevator.Passenger.getToFloor()1.01.01.0
elevator.Passenger.isStraightA()1.01.01.0
elevator.Passenger.isStraightB()1.01.01.0
elevator.Passenger.isStraightC()1.01.01.0
elevator.Passenger.Passenger(int,int,int)1.01.01.0
elevator.Passenger.Passenger(PersonRequest)2.01.02.0
elevator.Passenger.setDirection()1.04.011.0
elevator.Passenger.setStraight()3.04.07.0
elevator.Passenger.toString()1.01.01.0
scheduler.Scheduler.addPassengers()1.01.01.0
scheduler.Scheduler.addPerson(Passenger)1.01.01.0
scheduler.Scheduler.getPassenger(LiftStatus,int,LiftType)9.010.013.0
scheduler.Scheduler.getPassenger(LiftType)11.017.017.0
scheduler.Scheduler.getPassengers()1.01.01.0
scheduler.Scheduler.getPerson(int,LiftStatus,LiftType)4.07.07.0
scheduler.Scheduler.getSize()1.01.01.0
scheduler.Scheduler.isEnd()1.01.02.0
scheduler.Scheduler.isWaitGet(LiftType)11.014.017.0
scheduler.Scheduler.Scheduler()1.01.01.0
scheduler.Scheduler.setInputEnd(boolean)1.01.01.0
scheduler.Scheduler.subPassengers()1.01.01.0
Total107.0170.0204.0
Average1.81355932203389842.88135593220338973.457627118644068

分析

截取了部分调度器内复杂度较高的方法块,发现圈复杂度和模块复杂度较高的方法都是添加指令方法中的条件判断语句,这种条件判断的方式容错性特别低;在本次作业的第一版本中,笔者就是由于条件判断的逻辑出现了错误,导致最后一个点一直WA。

if (type == LiftType.A) {
if (person.isStraightB() || person.isStraightC()) {
continue;
}
} else if (type == LiftType.B) {
if (person.isStraightC() || person.isStraightA()) {
continue;
}
} else if (type == LiftType.C) {
if (person.isStraightA() || person.isStraightB()) {
continue;
}
}
总结:

通过IDEA的代码分析工具发现,只有方法内部出现了较多的 if - else语句,就会使方法内部的各项复杂度指数飙升,相应的改进方法为:在三部电梯中,重写获取指令的方法,利用子类的特性来避免大量了if-else语句。

关于完成进度问题:在第三次电梯中,我对代码进行了大量的重构,主要是将原来耦合度较高的方法进行拆分,并且取消了调度器作为“电梯大脑”的职能,而电梯能自主获取调度器中的指令并且运行。在之后的作业中,对前一次作业的重构应该放在本次作业开始之前,为本次作业争取到更多的测试时间。

自己程序的bug

这次出现的电梯停靠楼层不同,因此设置了1,15层作为电梯换乘,程序薄弱的地方就集中到了换乘楼层、3楼“神秘楼层“的处理两个地方,针对这两个薄弱环节,我将A,B,C 三部电梯先两两测试,然后最后三者并发测试……由于时间太短而且肉眼debug真的十分难受…导致强测结果boom了。。。

公测:由于某处添加乘客的时候忘记限制楼层,导致强测WA了好几个点:

本来条件应该是这样,但是后来修改某处bug时,忘记添加if判断条件,导致电梯在不该开门的楼层接送了乘客。

(下次一定好好测试,,,这次时间太紧了,一波重构再一波多电梯,剩余的测试时间不是很多);

添加了此处的if语句之后,就顺利修复了所有bug。

if (floors.contains(curFloor)) {
Passenger p = pickPerson();
checkArrive(p);
}

互测

采用的策略,通过自动生成数据,python搭建test评测机,自动化的对本房间内所有人进行测试。

总共hack了18次。

UML协作

Java设计模式的选择

  1. 生产者消费者模式

    在多线程中,生产者消费者模式是最常见的设计模式。此次作业中,输入线程作为生产者,电梯线程作为消费者,共享调度器这一对象(更准确的说法是:调度器中包含的一个指令队列)。

    为了保证线程安全和避免暴力轮询导致CPU超时,我采用了wait和notify机制:每当有指令加入请求队列时,notifyAll唤醒所有等待中的线程。

  2. 观察者模式

    电梯作为消息的发送者,当电梯状态改变时,通知调度器针对当前电梯的状态做出反应.

  3. 装饰模式

    包装personrequest类,增加了换乘功能和方向属性

  4. 单例模式

    调度器对象作为全局单例,防止程序某些地方建立多个调度器,影响程序的正确执行。

     

心得体会

在研究学长的博客中发现了一个与自己心态十分相似的一个细节,简言之: 为了作业而写作业

这种心态的表现形式主要有以下几种(较为极端的):

  • 老师讲这些与我们完成作业有什么关系,我们就是想完成作业而已。

  • 老师强调这些问题有什么用,我的程序是不能运行了还是怎么着?

  • 你说再多的代码规范一样会被钻牛角尖啊…

在一位大佬学长看来,这些心理简直是不可理喻的,甚至被学长批判为:这些人与技术流不是一路人。但平心而论,在面对OO如此庞大的工程架构时,有些人是难免会产生这种心理,但应该批判的不是这种浮躁情绪的产生,而是仍其肆意扩张。在OO第二次作业中,开始时我认为设置主请求这种做法不够简单,于是在电梯中维护了一个楼层的队列,电梯每经过楼层队列中的元素时,便检查是否有乘客需要下电梯……(这些都不重要),然而我想说的是,当写完第二次作业之后,我又意识到这样的架构看起来十分混乱,于是在第三次作业中重新组织了代码,逻辑看起来比第二次合理多了,但是在第三次作业中重构,是我对第三次作业的测试时间大大减少,这也导致公测时被爆出了非常明显的bug。

在面对复杂度如此高的作业面前,我确实产生过很多消极的负面的情绪,几个例子:

“我要赶紧写完这破电梯,还要复习OS呢”

这种浮躁的心态一直萦绕着我,直到我给自己定下目标:完成一件事的结果并不重要,重要的是完成一件事的态度。但我仔细分析了其中的原因,觉得OO这门课是否过于强调分数了??——结果同学们都变成暴躁老哥(Anyway……)来大学是为了学习的,说明白点就是为了以后能有一个优越的生活,那么我们必须明白——

  • “我究竟能从中收获什么?”

结论很简单:有所收获的,就是对的能收获更大的,就是更好的

昆仑课程的征途已经过半,愿与同学们再接再励,迎难而上~

转载于:https://www.cnblogs.com/1-Gary-1/p/10740700.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值