zoukankan      html  css  js  c++  java
  • OOP第二期作业总结

    三次作业设计&结构分析

    Project5

    1)设计策略

    在第一次电梯作业中,我采取的是生产者--消费者的模式,将请求队列作为共享对象存储在调度器中,调度器为单例模式,被输入线程和电梯线程共享;输入线程和电梯线程在同一时间点只能有一个访问请求队列。

    2)类图如下

    ElevatorController作为调度器,采取单例模式,被InputHandle类和ElevatorRun类共享。输出类和电梯类作为线程同步运行。Main函数中初始化线程,开始运行。

    3) 类的属性个数,方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模。

    在四个类中,输入类和电梯类作为Thread类的继承。

    方法个数及复杂度

     

    可以看到在第一次电梯作业中方法间的紧密度不高,主要集中在ElevatorRun.run()方法中,因为在run方法中,人的上下电梯操作、开关门操作都包含进来。

    类的复杂度

     

    依赖度分析

    与设计模式相符。

     

    优点

    本次作业结构比较清晰,将需求和电梯通过调度器进行衔接。具有可复用性,多个电梯也可以运用此模板。线程间共享调度器,只要给调度器上锁就(可能)不会有线程安全问题。因为调度器只有一个,采取单例模式,避免将调度器当作参数传入的方法。采取waitnotifyall结合的模式,避免轮询,降低CPU运行时间,更符合实际需求。

    缺点

    采取傻瓜调度模式,电梯性能较低,电梯run方法中将上下楼层、开关门写在一起,可扩展性不强。(其实调度器没有作为线程而是采取共享对象的方式不知道是否合适。)

    通过UML协作图展示线程之间的协作关系

    在此先附上一片关于协作图内容的博客:

    https://blog.csdn.net/MrBaymax/article/details/81286321

     

    设计原则自查

    SRP原则 对于输入类,只负责将请求传递给调度器。调度器功能有三个,接受请求、发布请求以及判断当前队列中是否有请求。电梯功能包括:从调度器中取请求、电梯运作、开关门、人进来出去的操作。第一次作业中电梯类run方法包含全部操作,显得电梯比较乱。
    OCP原则 从第一次的傻瓜电梯到第二次的捎带,总体上没有变化。对于调度器来说会增加一些东西,电梯也是通过扩展来完成。但是run方法没有扩展性,只能改写。
    LSP原则 对于第一次作业,,如果说要继承的话,应该是电梯进行继承,但我的电梯几乎没有可扩展性,因为运动在电梯run方法类将一个人从进门到出电梯的状态一气呵成写完,应该将各步骤进行拆分。
    ISP原则 没有采用接口,无冲突。
    DIP原则 由于我的调度器是实例化的,采用单例模式,因此化抽象为具体,有点违背依赖倒置原则。电梯的开关门时间、运行一层楼的时间也是固定的,感觉应该是实例化电梯时作为参数传进去会好一点。

     

    Project6

    1)设计策略

    在第二次电梯作业中,我沿用第一次作业的思路,采取的仍然是生产者--消费者的模式。将请求队列作为共享对象存储在调度器中,调度器为单例模式,被输入线程和电梯线程共享;但这次因为考虑捎带,我在调度器中将主队列分成上下队列,即我的调度器中存在三个队列。设计思路:1.电梯类中暂时没有请求,从主队列remove 0位置的请求,根据请求方向顺带删除上或下队列的元素;2.电梯中有请求,在运行过程中根据电梯方向在上下队列中remove元素,并且在主队列中删除相应元素。捎带原则是,电梯在运行过程中,如果该方向有人,则捎带。

    2) 类图如下

     

    第二次电梯作业没有认真思考优化算法,采取指导书的ALS算法,可以看到在上回基础上新增一个类作为指令的修改版,增加该指令的方向。

    3) 类的属性个数,方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模。

    复用第一次作业的类,因此属性不变。

    方法个数及复杂度

     

    第二次作业中可以看到出现有点病态的方法,在电梯类和调度器中。电梯的run方法和getTimeOper均有三个while循环的结构,都涉及到捎带问题。getTimeOper方法用于:在电梯队列为空运行至主请求间允许捎带。

     1 private void getTimeOper(long end) {
     2         try {
     3             if (end == floor) {
     4                 return;
     5             } else if (end > floor) {
     6                 flag = 1;
     7                 isUp = true;
     8             } else if (end < floor) {
     9                 flag = -1;
    10                 isUp = false;
    11             }
    12             // this floor still has request
    13             while (addElemt()) {
    14                 continue;
    15             }
    16             while (minusElemt()) {
    17                 continue;
    18             }
    19             plusTimeClose();
    20             while (floor != end) {
    21                 if (floor == 0) {
    22                     continue;
    23                 }
    24                 Thread.sleep(UPORDOWN);
    25                 floor += flag;
    26                 getArrive();
    27                 while (addElemt()) {
    28                     continue;
    29                 }
    30                 while (minusElemt()) {
    31                     continue;
    32                 }
    33                 if (floor != end) {
    34                     plusTimeClose();
    35                 }
    36             }
    37         } catch (Exception e) {
    38             e.printStackTrace();
    39         }
    40     }
    View Code

    类的复杂度

     

    在捎带问题上,我采取循环模式。即如果当前队列中有人进入电梯队列,即捎带成功,再次进行寻找直到没有捎带产生;删除模式也是类似。本身在电梯类run函数就是循环,相当于嵌套有三层循环,因此总循环复杂度是比较高的。

    依赖度分析

     

    每个类直接或者间接相互依赖的类不存在,与上回一样。大体上也有设计结构相符。

    优点

    在第一次作业基础上修改,没有重构,可复用第二次作业代码。电梯与输入线程依旧是用调度器进行消息传递。

    缺点

    间接改变personRequest类,有破坏封装性的意味。电梯类中存在太多while循环,复杂度增加。由于采取指导书的原则,性能方面并不高。无脑采用synchronize加锁,使用不当。调度器写得不太好,开了三个静态数组,分别是总队列、上队列和下队列,没有将每个personRequest的方向灵活运用好,为第三次作业死锁埋bug

    通过UML协作图展示线程之间的协作关系

     

    设计原则自查

    SRP原则 本次调度器职责增多,由于采取捎带策略,调度器需要在队列中找到电梯正在运行楼层可以捎带的人员,同时进行删除操作,同时电梯需要判断是否关门,我有两个取元素的方法,一个是取元素并删除,另一个是只取元素不删除。电梯类中,本来也是一run到底的,但是方法超60行,于是拆分成很多小方法,最后电梯类里面有超过200行代码,分别有电梯开关门、增加与删除元素、取出主请求、运行到下一层楼、运行到主请求。除了捎带涉及到增加与删除元素的方法,方法间耦合度不高。
    OCP原则 对于调度器,在第一次基础上只用额外增加两个队列,以及这两个队列的删除和增加方法;但是电梯类,虽然沿用第一次思路,但是修改的地方还是很多的,将电梯运行进行拆分,因为方法在SRP原则中有所改进,也更符合OCP原则。
    LSP原则 采取ALS算法,电梯运行效率不高,但是应该算得上一个可继承的电梯,对于每个小方法,都可改写。比如增加元素,我选取的是当前楼层为起始楼层并且需求与电梯运行方向一致的请求。
    ISP原则 未体现
    DIP原则 与上次类似,采取单例模式,电梯与调度器直接关联,每增加一个电梯,调度器相应都得增加请求队列,电梯越多,调度器越冗长。第三次作业有体现。

     Project7

    1)设计策略

     电梯运作模式沿用第二次思路,每个电梯从调度器中对应的队列取元素,但是每个电梯的请求可能存在换乘问题,因此三个电梯对所有队列都会存在访问,这会造成自己的线程不安全。因为沿用第二次作业,开始思路是设计三级调度器,一级为主队列,即读入队列;二级为三个电梯主队列;三级为三个电梯的上下共6个队列。结构应该比较清晰,但是删除增加元素时可能存在问题,最后换成一级队列。将isUp的属性合理应用到电梯中。因此最后有inputHandle输入线程、三个电梯线程和Main函数作为启动线程。

    2) 类图如下

     

    3) 类的属性个数,方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模。

    本次作业中,想采取工厂模式,象征性地建了三个工厂,分别用于造电梯123.其中电梯123,内容和第二次电梯作业几乎一致。调度器为ElevQueue,依旧采取单例模式。

    方法个数及复杂度

     

    第三次作业中首先是第二次作业复杂度的方法,其次是将指令放入队列中,如果不能直达进行拆分,在这里是否拆分我进行while循环的判断;以及在对队列中的元素进行选取到电梯中,首先是判断电梯是否为空,有try,catch结构,其次是队列中是否存在可执行请求,同样也是try,catch结构。

     1 public synchronized PersonRequestNew getQueueThr() {
     2         while (isQueThrEmpty()) {
     3             try {
     4                 if (!ElevQueue.getInstance().isEnd()) {
     5                     wait();
     6                 } else {
     7                     break;
     8                 }
     9             } catch (Exception e) {
    10                 e.printStackTrace();
    11             }
    12         }
    13         if (!isQueThrEmpty()) {
    14             while (true) {
    15                 try {
    16                     for (int i = 0; i < personReqThr.size(); ++i) {
    17                         PersonRequestNew person = personReqThr.get(i);
    18                         if (person.isStatus()) {
    19                             return personReqThr.remove(i);
    20                         }
    21                     }
    22                     wait();
    23                 } catch (Exception e) {
    24                     e.printStackTrace();
    25                 }
    26             }
    27         } else {
    28             return null;
    29         }
    30     }
    View Code

    类的复杂度

    三个电梯的循环程度和第二次作业是一致的,对于调度器的循环程度如此之高,其实也存在我的调度器采用单例模式,不好扩展,调度器内部设置三个对应的电梯队列,因此加元素或者寻找元素时均要对三个队列都写一遍。

    依赖度分析

    优点

    我认为本次作业采用工厂模式写看似冗余,其实更切近实际生活。依旧采用wait与notifyall结合的模式来解决调度器和电梯线程的关系,结构清晰,对输出可能出现线程不安全进行加锁。

    缺点

    采用工厂模式显得代码冗余,和同屋的代码相比,类的数量是两倍或以上。调度器采用单例模式,里面的电梯队列没有扩展性,且存在大量一样的操作,只是相应要操作的队列不一样,没有很好解决此问题。依旧是无脑加锁问题。(写出的代码感觉上没有怎么运用到所学知识)。

    通过UML协作图展示线程之间的协作关系

     

    设计原则自查

    SRP原则 调度器中每个方法确实只有一个功能,但调度器需要维持三个队列的状况,三个电梯队列是作为单例模式成员存在,容易受到外部因素,比如加电梯,调度策略更换的干扰。
    OCP原则 第三次作业沿用第二次的电梯,由于每个电梯可停靠楼层不一致,调度器分配请求时进行考虑。调度器职责进一步增加,类代码长达近450行。相对于一个电梯的调度器基本完全改写,同时对日后增加电梯也需要改写,所写的类不符合此原则。对于电梯类,由于调度器不含上下队列,因此在增加元素时中需要换方法,同时涉及换乘,在删除元素时也要增添方法。其他方法不变。
    LSP原则 这次的电梯类都是继承Elev类,三个电梯实际上用同一个模板,因为电梯属性都类似,上下楼层、开关门、捎带,不同的是运行时间、承载量、请求队列。采取工厂模式,因此三个电梯我没有传参数进去来改变,而是写了三个电梯类。
    ISP原则 本次实现一个工厂接口,接口里面有一个create Elev的方法可以改写,为日后增加多部电梯提供可能。
    DIP原则 调度器采用单例模式造成三个队列均是单例模式的元素,每个电梯中在抽象的时候就用上实例化的调度器,并且不同的电梯对应的队列不同,调度器中队列发生变化,电梯类也要进行修改,不太清楚这样是否合适。

    分析程序中的BUG

    Project5

    在第五次电梯中我采用傻瓜调度原则,还是翻车了。强测过程中,由于我在InputHandle类的run方法中设置30条指令(含终止Ctrl+D命令),导致如果是30条请求时,无法将终止命令输入,造成超时。在此附上指导书要求当然,经过这次作业,我觉得自己设计中应该存在容错机制--裕度设计!同时也要进行边界测试,这一直是我容易疏忽的地方。在自己写程序过程中,也出现过一些问题:

    1.程序在电梯中直接调用调度器队列是否为空的方法,导致一直为空,无法在电梯线程中用wait和notifyall解决。于是将取元素的操作在调度器中进行,得以解决。

    2.程序无法终止,对读入的null信号无法进行处理,最后是将null也放入调度器中,当电梯发现读入为null时线程结束。

    Project6

    第六次作业中,再次翻车。采用指导书的ALS算法,本来应该比较容易实现。问题是在电梯类中的floorOper方法:寻找主请求的过程中,我采取捎带,如果在同一方向,不论是否所到楼层高于主请求楼层都上去了,可能出现电梯内部捎带队列存在请求方向不一致的请求。因此我让每次电梯的运行方向是电梯中第一个请求的方向。在电梯运行过程中,电梯方向的语句没有及时更新,导致出现17楼和-4楼的情况,修复bug的时候也就是补一句话。不过这回出现和修复bug让我觉得不论修复bug是多么简单,程序出错了对用户来讲这个程序就是垃圾,有同学也说到这显示出自己写出的bug是多么严重。至此,我的电梯还不涉及线程安全类的问题。在设计思路上,对捎带确实没有考虑周到,我觉得应该学习一些网上电梯的捎带算法,借鉴别人的算法来改进自己的捎带模式。

    Project7

    第七次作业中,强测安全通过。但是弱测,由于自己线程不安全的问题导致每次的rtle的测试点都不太一样,甚至一条请求的测试点都会超时。我不太清楚是不是与自己无脑锁有关,开始我采用三级调度器,自认为思路清晰,交上去第一次也没有出现问题。但是后来交同样版本便出现rtle,自己本地如何用文件读入都跑不出评测机的结果。在交上去出现问题前,我还花了一早上走查代码确定没有问题。不知道会不会是在电梯类中调用三级调度器加锁方法,三级调度器会调用二级调度器加锁方法,A电梯会改变B电梯的元素情况,B、C电梯类似。但是我在所有对队列进行操作的地方均在方法上加锁。理论上应该不会出现死锁问题,还请大家畅谈一下自己出现线程不安全情况~总之,我觉得可能是三级调度器的问题,只好重构为一级调度器,至少在弱测和强测是ok的,至于是否仍有线程不安全的问题,我尚不清楚。

    发现别人bug采取的策略

    在电梯第一次作业中,我发现我们屋里有人写了捎带,我仔细研究他的调度策略,没有发现存在问题。在第二次测试中,其实我没有结合他们的代码来写特殊的评测数据,只有一些通用的边界测试。第三次测试中,重点考虑换乘是否正确,最后发现自己写的测试用例大家都没有问题。由于不太会用评测机,因此在互测以及自己的测试中没有用上,我觉得这项技能应该要学习。并且,在第三次作业中,我发现大家的思路都不太一样,代码风格也不同,没有自动化数据不好查错。当然,我觉得有时间的话,对于屋里一些优秀的代码应该要好好学习。

    在这一单元,测试用例的思考方向会更多考虑在线程安全上,从评测机测评方向可以看出:CPU时间和实际运行时间;大家电梯出现的问题:没有送达正确、电梯吃人、多次出或进入电梯、出现错误楼层等等。同一份代码交上去可能会出现评测正确,也可能出现评测错误的结果。而第一单元:一般是求导出错或者是格式判断问题。这一单元格式问题已经写好,无需考虑。而线程安全本身考虑的因素很多,同时我们也不能通过打印语句输出相应结果,因为println本身就涉及到线程安全问题。拿自己而言,最好的方式是走查代码,发现可能出现的死锁。因此第一单元更多地考虑思路正确性问题,第二单元更多考虑线程安全性问题。

    心得体会

    设计原则

    SOLID原则其实是相互关联的,自己总结后发现,单一功能原则没有设计好后面的也会受牵连。从第一次作业开始,我就采用单例模式,本来想在第二次作业中设计为观察者模式,但是发现不太会写也没有问同学,因此就放弃了。第三次作业中照葫芦画瓢套用工厂模式,电梯有点冗长,因此我对工厂模式是否适用于本期作业还是有点疑惑的。个人认为电梯的调度器只有一个,采取单例模式应该是比较好的设计,同时从单例模式的写法中我也加深了对锁的理解。但是感觉自己的电梯中用到调度器的实例原件也不是一个好方法。在三次作业中,感觉总体架构没有怎么改变,复用性还是很高的。不过第三次作业迷之rtle导致调度器进行小范围的重构,线程问题理解堪忧。

    线程安全

    第二期作业虽然完成,也只是从基本上理解多线程。明白wait和notifyall与wait和notify结合的不同、轮询对CPU的消耗有多大、利用jprofilers来分析自己的线程(看了也不会找bug)、一般情况下如何产生死锁、不能用println进行debug;从分享经验的同学身上学到打开多线程debug的正确方式。一般来说,产生死锁是因为多个线程占据不同资源,而他们在接下来的过程中需要的资源已被占据,较好的解决思路是锁住的对象顺序保持一致,这样大家虽然先不用到该资源,但是在同一时间就能保证只有一个线程能够顺理进行。其实因为我的调度器采用共享对象而不是线程方式,对于其他同学出现的线程问题大部分还是没有的,第一次和第二次作业,线程不安全也只会在读取和删除调度器队列中的元素出现;但第三次,首先bug我无法复现,其次我觉得单纯无脑锁应该是不会有问题的,在一个时间点只有一个对象对该类进行访问,应该是不会产生死锁问题。不过我找机会还是看一下自己的设计思路,更好理解synchronize的思想。

  • 相关阅读:
    vue2.0 微信分享
    小程序开发:canvas在画布上滑动,页面跟着滑动问题
    前端AES解密
    vue2上传图片到OSS
    vue给不同环境配置不同打包命令
    vue页面绑定数据,渲染页面时会出现页面闪烁
    解决微信浏览器无法使用window.location.reload刷新页面
    vue列表拖拽组件 vue-dragging
    千万别在Java类的static块里写会抛异常的代码!
    linux shell的一些配置
  • 原文地址:https://www.cnblogs.com/zt17373358/p/10742944.html
Copyright © 2011-2022 走看看