• 面向对象设计与构造第二单元总结


    设计策略

    第一次

    架构模式采用生产者-消费者模式,输入线程作为生产者,电梯作为消费者,共享对象为由一个自定义的Floor对象组成的数组构成。Controller类只起告知电梯线程输入结束的作用。

    整体上采用LOOK算法的调度策略,由于第一次作业的电梯没有容量限制,所以可以采取尽可能把乘客关在电梯里的办法。比如当前电梯在1楼,现在有1号乘客要从1楼到2楼,2号乘客要从15楼到2楼。最快的方法是在1楼接到1号乘客后,直奔15楼,即使中途到了2楼,也不让这个乘客下电梯,直到到15楼把2号乘客接到,再回到2楼,让1号乘客和2号乘客一起下电梯。这样可以节省一次在2楼开关门的时间。尽管在现实中会给1号乘客极其不好的乘电梯体验,但是这样的确是在满足规则的条件下最快的。

    第二次

    架构模式采用生产者-消费者模式,输入线程作为生产者,电梯作为消费者,共享对象为由一个自定义的RequestQueue对象。这里不再使用第一次作业的Floor对象数组作为共享对象的原因是强测限制了最大指令数为50条,即使挨个遍历请求队列里的请求也不会有大问题,并且会在锁的问题上产生问题。如果锁整个Floor数组,那么Floor数组就和单独的请求队列一样,没有存在的必要。如果锁单个Floor,那么锁住了某个Floor,另一个Floor的数据可能会发生改变,带来线程安全上的问题。

    由于加入了负楼层,-1楼到-3楼,使得楼层变得不连续,这样使得在调度请求、电梯运行时都极不方便。因此,受到操作系统中虚拟地址与物理地址的启发,将低楼层到高楼层依次映射到虚拟楼层上。这样,在使用电梯请求的时候,使用者可以使用到连续的楼层,比如电梯上楼时只需要将虚拟楼层+1,无需考虑实际楼层是否处于-1层。封装了与PersonRequest类不同的MyRequest类,其中楼层既可以返回实际楼层(方便输出用),也可以返回虚拟楼层(方便计算用)。

    整体上继续沿用第一次作业的LOOK算法的调度策略,但是与第一次作业不同,第二次作业的电梯有容量限制,所以乘客能下就尽可能先下,以免占据电梯空间让本来可以捎带的乘客捎带不了了。

    第三次

    第三次架构模式为Worker-Thread模式。输入线程充当客户Client,发出请求,Controller类充当工厂车间Channel,电梯充当工人Worker。

    电梯调度方面,由于电梯出现不能直达的情况,必须要把一个请求拆分为多个请求。由于动态拆分过于复杂,这里的拆分采用静态的划分策略,即拆分策略与电梯当前的运行状态无关。我采取了Floyd算法,在各楼层之间求最短路径。

    优化方面,由于第三次加入了乘客等待时间作为性能指标,所以不再采取LOOK算法,而是采用SSTF算法(本质上是贪心算法),最终也获得了比较不错的性能分。

    第三次作业架构设计的可扩展性

     设计模式中的SOLID原则,分别是单一原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。前辈们总结出来的,遵循五大原则可以使程序解决紧耦合,更加健壮。

    单一责任原则

    指的是一个类或者一个方法只做一件事。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化就可能抑制或者削弱这个类完成其他职责的能力。

     建立了一个MyReQueuestFactory工厂类,传入一个官方的PersonRequest,就可以返回拆分请求后的一组请求。如果以后需要更改拆分策略,在这个工厂类里进行修改即可,无需改动其他部分。同时,电梯也不会参与请求的调度过程,如果要修改调度算法,直接在Controller类里修改即可。

    开放封闭原则

    对扩展开放,对修改关闭。意为一个类独立之后就不应该去修改它,而是以扩展的方式适应新需求。

    第三次、第二次的电梯除了修改运行策略外,其他均重用了第一次电梯的方法,因此开放封闭原则还是做的比较好的。

    里氏替换原则

    所有基类出现的地方都可以用派生类替换而不会程序产生错误。子类可以扩展父类的功能,但不能改变父类原有的功能。

    本单元作业并没有利用到继承,因此在此不讨论里氏替换原则。

    接口隔离原则

    类不应该依赖不需要的接口,知道越少越好。

    本单元作业也没有利用到接口,因此在此不讨论接口隔离原则。

    依赖倒置原则

    指的是高级模块不应该依赖低级模块,而是依赖抽象。抽象不能依赖细节,细节要依赖抽象。

    我在依赖倒置原则这方面做得不太好,因为调度器内高度依赖电梯的内部结构,包括电梯到底是使用

    基于度量分析自己的程序结构

    协作图

    类图

    第一次作业

     

    第二次作业

     

    第三次作业

    分析自己程序的bug

    自己在强测、互测中均未发现bug。

    在第三次作业自己测试的过程中,发现了CPU超时的bug。原因在于我判断电梯进入wait状态的条件沿用了第二次作业的,写成了hasInput() && elevCar.isEmpty() && tashQueue.isEmpty(),这样的话,如果输入线程还没结束,但电梯已经做完了自己的事情的时候,就不会进入wait状态,而是一直在轮询,最终导致CPU超时。

    分析自己发现别人程序bug所采用的策略

    本单元做得不太好,没有手写评测机(摸了)。一部分原因是自己确实比较忙,另一部分原因是多线程的评测机确实比较难写。因此策略一是采取读别人的代码,然后针对性的hack。而是构造边界数据,如在临界时间加请求,测试超载等。

    心得体会

    本单元游戏体验比较好,感谢评论区各位ju佬提供的各种优化策略以及各位助教给的宽松的性能分机制。

    在多线程的测试里面,一次测试正确并不能说明程序对于这个测试用例是正确的,有可能同一个测试样例运行999次都是正确的,但是评测的时候那1次运行错误,导致WA。对于这个问题,没什么好的解决办法,只能是尽可能的多测试,尽可能减少错误发生的概率。

  • 相关阅读:
    JS中的间歇(周期)调用setInterval()与超时(延迟)调用setTimeout()相关总结
    jQuery中的height()、innerheight()、outerheight()的区别总结
    单行及多行文本溢出以省略号显示的方法总结
    Android图片缩放 指定尺寸
    Android开源SlidingMenu的使用
    说说Android应用的persistent属性
    Android使用init.rc触发脚本实现隐藏内置应用
    android之实现上下左右翻页效果
    Android中播放声音
    Android中StatFs获取系统/sdcard存储(剩余空间)大小
  • 原文地址:https://www.cnblogs.com/Luocx/p/12715852.html
Copyright © 2020-2023  润新知