OO第二单元总结

OO第二单元总结

目录
  • OO第二单元总结
    • 基于多线程的设计分析
      • (1)HW5:设计简单的可稍带单部电梯
      • (2)HW6:设计多部可稍带电梯
      • (3)设计多部多线程可捎带可换乘电梯
    • 电梯第三次作业架构设计的可扩展性
    • 基于度量的程序结构分析
      • HW5
      • HW6
      • HW7
      • 总结
    • 基于bug的程序分析
    • 基于互测的程序分析
    • 心得体会


基于多线程的设计分析

(1)HW5:设计简单的可稍带单部电梯

  第一次的作业要求较为简单, 由于第一次尝试多线程设计, 便使用了较为基础的生产者-消费者模型, 将用户的输入请求当作生产者, 接受请求的电梯作为消费者, 而请求队列里的各个请求便是二者共有的资源。整体设计分为两个线程, 其一是elevator线程, 负责elevator的接受请求, 等待请求, 判断下一个运动方向, 运动, 开关门与进出乘客, 其二是PersonInput线程, 负责读入请求, 将请求线程安全的输入到请求队列中, 并提供输入结束的信号, 作为elevator线程结束的条件之一。

public class Main  private static boolean elevator_close = false;

if (request == null) {
    synchronized (Main.class) {
        Main.setElevator_close(true);
        }
    break;
}

  这样通过锁住Main.class,能在确保线程安全的情况下安全的修改读取电梯现成的结束标志, 确保电梯不出现卡死或者无限wait的情况, 这也是整个多线程比较重要的一点。
  调度策略方面由于是第一次电梯设计, 大部分时间花在了多线程的实现上, 因此电梯调度采用了最简单的ALS,在这里就不再赘述了。

(2)HW6:设计多部可稍带电梯

  本次作业与第一次相比增加了电梯的数量, 其余没有太大的变化。秉持解耦合的观念, 我一共设计了Person,Elevator(电梯线程),Controller(调度线程),Main四个类, 由于每个电梯需要设计一个单独的线程, 因此我使用private ArrayList> elevatorsQueue来存放所有电梯的请求队列, private ArrayList requestQueue来存放所有请求, ArrayList elevators来存放所有电梯线程, 这样的通过arraylist实现的整体结构, 在修改(添加删除)线程共有资源, 例如第i个电梯的请求队列时, 只需要synchronized (elevatorsQueue.get(i))即可实现对共享资源的保护, 保证线程安全。
  而本身Elevator线程与Controller线程除去共享相同资源外没有任何关联, 这比起我第一次作业设计的需要Elevator初始化Controller,同时需要Controller初始化Elavator这样的蛇皮橡皮糖结构起到了很大的解耦合作用。
  调度算法层面, 吸取了第一次傻瓜式ALS的中低档性能分的惨剧, 这次我尝试了Look算法, 在提交DDL前3h改成了室友“推销(划去)”的电梯内部贪心算法, 但由于测试时间罢太够, 有一个小细节忽略了, 导致强测惨剧的出现QAQ,下面先贴出正确的调度策略代码,BUG分析部分再来分析哭诉。

private void nextDirection() {  //局部贪心
        if (passengers.isEmpty()) {     //empty
            direction = 0;
        }
        else {
            int minJourney = 10000;
            int chooseWho = 10000;
            int i;
            for (i = 0; i < passengers.size(); i++) {
                int journey = 100000;
                if (!passengers.get(i).isFinished() && passengers.get(i).isInSide()) {
                    journey = Math.abs(passengers.get(i).getTofloor() - floor);
                }
                else if (!passengers.get(i).isFinished() && !passengers.get(i).isInSide()
                        && numIn < 7) {
                    journey = Math.abs(passengers.get(i).getFromfloor() - floor);
                }
                if (journey < minJourney) {
                    minJourney = journey;
                    chooseWho = i;
                }
                if ((journey == minJourney) && direction != 0) {    //同距离时选择同向的
                    if (passengers.get(i).isInSide()) {
                        if (direction * (passengers.get(i).getTofloor() - floor) > 0) {
                            chooseWho = i;
                        }
                    }
                    else {
                        if (direction * (passengers.get(i).getFromfloor() - floor) > 0) {
                            chooseWho = i;
                        }
                    }
                }
            }
            if (chooseWho == 10000) {
                direction = 0;
            }
            else {
                if (passengers.get(chooseWho).isInSide()) {
                    if (passengers.get(chooseWho).getTofloor() > floor) {
                        direction = 1;
                    }
                    else {
                        direction = -1;
                    }
                }
                else {
                    if (passengers.get(chooseWho).getFromfloor() > floor) {
                        direction = 1;
                    }
                    else {
                        direction = -1;
                    }
                }
            }
        }
}

  除去强测GG的点, 剩下点的性能分都达到了99+, 说明本身调度策略在本次作业的随机数据的环境中效果罢错。
  忘了提, 用户选择电梯的方案我设计成:如果电梯为空, 就直接放入请求;若均不空, 则根据电梯的负载量与与请求的距离选择;若上述条件均相同, 则通过一个随机数对条件相同的电梯数目取余得到放请求的电梯编号, 这样设计经过测试效果良好。

(3)设计多部多线程可捎带可换乘电梯

  本次作业与第二次的多部电梯相比, 增加了两个要求, 其一:随时根据输入加入新的电梯线程, 这个好办, 只需根据前文提到的方法synchronized (elevators)将存放所有elevator线程的队列锁住即可安全进行。其二:增加到达楼层限制与换乘方案, 这里我通过新添加一个Change线程来专门控制private ArrayList changeQueue, 其线程保护思路与requestQueue类似, 在访问修改时对数组加上同步锁即可。
  在调度算法方面, 测试了三种调度策略后, 我选择了最稳妥简洁的LOOK算法对电梯运行进行控制, 主要部分如下:

  private void nextDirection() {  
        if (direction == 0) {   //stoping
            for (Person person : passengers) {
                if ((person.getFromfloor() > floor && !person.isInSide()
                        && !person.isFinished() && numIn < maxSize) ||
                        (person.getTofloor() > floor && person.isInSide()
                                && !person.isFinished())) {
                    direction = 1;
                    break;
                }
                else if ((person.getFromfloor() < floor && !person.isInSide()
                        && !person.isFinished() && numIn < maxSize) ||
                        (person.getTofloor() < floor && person.isInSide()
                                && !person.isFinished())) {
                    direction = -1;
                    break;
                }
                else {
                    direction = 0;
                }
            }
        }
        if (direction == 1) {     //uping
            for (Person person : passengers) {
                if ((person.getFromfloor() > floor && !person.isInSide()
                        && !person.isFinished() && numIn < maxSize) ||
                        (person.getTofloor() > floor && person.isInSide()
                                && !person.isFinished())) {
                    direction = 1;
                    break;
                }
                else {
                    direction = 0;
                }
            }
        }
        if (direction == -1) {    //downing
            for (Person person : passengers) {
                if ((person.getFromfloor() < floor && !person.isInSide()
                        && !person.isFinished() && numIn < maxSize) ||
                        (person.getTofloor() < floor && person.isInSide()
                                && !person.isFinished())) {
                    direction = -1;
                    break;
                }
                else {
                    direction = 0;
                }
            }
        }
        if (passengers.isEmpty()) {     //empty
            direction = 0;
        }

    }

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

  正如前文提到的, 我使用了Person,Elevator,Main,Controller,Change五个类, 三个线程完成了整体架构的设计, 且类与类, 线程与线程之间的解耦合我认为做得比较到位, 即类与类之间不会出现相互关联的情况, 且类里面的方法本身也耦合度极低, 修改添加起来十分方便。因此, 譬如如果要加入对电梯承重的限制一条件, 可以直接在Elevator与Person类里面加入Weight和maxWeight属性, 并在Elevator中加入whetherOweight()方法, 返回一个Boolean,最后在PersonIn方法中加入限制条件即可。
  又由于我的请求, 不同电梯的线程, 换乘请求都是通过不同Arraylist的形式保存的, 因此对于线程的拓展改变, 只需要在使用改变Arraylist时先对其加上同步锁, 再进行操作, 就能保证现成的安全, 十分方便。


基于度量的程序结构分析

HW5

OO第二单元总结_第1张图片

OO第二单元总结_第2张图片

OO第二单元总结_第3张图片

  可以发现由于第一次书写多线程, 我没能很好的将不同线程资源分开, 导致Controller和Elevator类的耦合度极高, 且将能划入Person类的方法在Elevator类中混淆使用了, 导致Elevator类十分冗杂, 这在我之后的作业中会得到优化。

HW6

OO第二单元总结_第4张图片

OO第二单元总结_第5张图片

OO第二单元总结_第6张图片

  这次明显我通过将不同线程和线程共享的资源用不同且唯一的Arraylist进行储存, 使得类与类,Elevator线程与Controller线程之间的信息交换通过且仅通过private ArrayList requestQueue, 极大降低了程序的耦合度, 相较于第一次实现了很大的优化。

HW7

OO第二单元总结_第7张图片

OO第二单元总结_第8张图片

  这次的程序结构设计直接承接上一次, 多的Change线程也是通过且仅通过private ArrayListchangeQueue与Elevator和Controller线程进行交互, 在解耦合方面我认为实现的较为不错。

总结
  • SRP 每个方法与类都实现了唯一且明确的功能。
  • OCP 后两次作业的设计全部由第一次迭代下来, 只进行了功能上的拓展, 相比于第一单元的重构重构重构有了质的飞跃, 舒服的一。
  • LSP 没有使用继承, 体现得不明显
  • ISP 没有使用接口, 体现得不明显。
  • DIP 类与类之间的依赖简洁清晰。

基于bug的程序分析

  • HW5 线程较为简单, 调度算法也是最基础的ALS,并没有出现问题
  • HW6 由于最后更换到贪心算法过于仓促, 导致我出现了极其愚蠢, 愚蠢之极的漏洞:由于在贪心算法每次判断下一个moveDirection时要扫描一部电梯的全部请求队列, 选择距离最短的请求, 以此来确定方向, 而我在遍历isInside == False的乘客时, 没有加入whether numIn < maxSize的限制, 导致如果电梯请求队列人数超过负载数目, 而电梯距离请求最近的两人又恰好在超过的人中, 就会出现电梯上一层楼, 下一层楼, 上一层楼, 下一层楼, 上一层楼, 下一层楼, 上一层楼, 下一层楼, 上一层楼, 下一层楼, bulabula 的无限套娃情况, 实属弱智错误。
  • HW7 由于继承HW6,没有太多问题。

基于互测的程序分析

  主要通过python生成随机数据定时投放到程序中测试, 感谢讨论区大佬给出的实现方法(可惜互测时没中人。


心得体会

  YYSY,感觉第二单元的学习真正意义上让我体会到了OO的魅力,除去学会了多线程的构造,测试,调度以外,能从第一次作业的结构一次次继承到后面,而不是第一单元面向过程码码的一次一次重构,就让我倍感成就感了(不知为啥感觉如果能把电梯放到第一单元学习或许更好)。希望下个单元的学习能顺顺利利吧。

你可能感兴趣的:(OO第二单元总结)