第一次作业
设计思路与感想
第一次作业是要求有捎带的电梯实现, 第一次作业是花费的时间比较长的一次,花费了很多的时间去思考架构的问题.起初是想要搞三个线程的:输入线程,调度器线程和电梯线程,想要搞一个连锁的生产消费模型,但是在调度器线程和电梯线程在交互的时候出现了一些问题.
- 对run方法操作最小化的思考,
- 对于输入线程和调度器线程来说就是比较简单的,对于电梯线程我考虑了要不要添加一些非最小化的操作,比如:电梯加入moveTo()方法.
- 这样的方法在while(true)里面是不合适的,完全可以通过每循环一次移动一楼
- 这个moveTo具有占用大量时间的问题,因为上楼就要有0.4s的消耗,上楼这段时间没有办法执行下一次循环来响应请求.
- 所以run方法中含有while(true)建议每次循环都做最少的事,由小组成一个大的
- 对于三个线程即:输入,调度器,电梯线程的思考
- 这块我起初是想实现三个线程但是操作上还是出现了问题,虽然构建了流水线来搞,但是电梯线程有一个上下楼的等待时间,这个对于调度起来说,给不给电梯捎带请求是很难的一点,这个时候就需要加入一个电梯的返回来实现,而我没有这么做.
- 我是直接将电梯线程join到了调度器线程里面,这个时候电梯内部没有了while(true), 等到电梯执行完了调度器再继续执行.
- 交了作业以后,我发现其实此时的电梯线程已经没有必要再做成一个线程了,它完全就是一个普通的对象了.于是在下一次作业,将三线程变成了两线程
- 本次作业采用了als算法,性能不是很好,还是用look性能好一些.
类图分析
- 类之间调用的复杂度比较高,还是有一定的改进空间
度量分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
"Command.Command(boolean)" | 1 | 1 | 1 |
"Command.addCommand(int)" | 1 | 3 | 3 |
"Command.getCommand()" | 1 | 3 | 3 |
"Dispatcher.Dispatcher(LinkedList |
1 | 1 | 1 |
"Dispatcher.checkIfSomeBodyGetIn()" | 3 | 2 | 3 |
"Dispatcher.checkIfSomeBodyGetOff()" | 3 | 2 | 3 |
"Dispatcher.closeDoor()" | 1 | 2 | 2 |
"Dispatcher.downFloor()" | 1 | 2 | 2 |
"Dispatcher.getIn()" | 3 | 4 | 4 |
"Dispatcher.getOff()" | 3 | 3 | 3 |
"Dispatcher.isSameDir(PersonRequest)" | 3 | 4 | 5 |
"Dispatcher.openDoor()" | 1 | 2 | 2 |
"Dispatcher.run()" | $color{red}{6}$ | $color{red}{13}$ | $color{red}{15}$ |
"Dispatcher.setStop(boolean)" | 1 | 1 | 1 |
"Dispatcher.upFloor()" | 1 | 2 | 2 |
"Elevator.Elevator(LinkedList |
1 | 1 | 1 |
"Elevator.arrive()" | 1 | 1 | 1 |
"Elevator.close()" | 1 | 1 | 1 |
"Elevator.down()" | 1 | 2 | 2 |
"Elevator.getFloorNumNow()" | 1 | 1 | 1 |
"Elevator.getIrq()" | 1 | 1 | 1 |
"Elevator.isRuning()" | 1 | 1 | 1 |
"Elevator.open()" | 1 | 2 | 2 |
"Elevator.run()" | 1 | 5 | 5 |
"Elevator.setRuning(boolean)" | 1 | 1 | 1 |
"Elevator.up()" | 1 | 2 | 2 |
有一个方法的复杂度比较高,其他的方法还好.这个run方法里面多次调用了elevator里面的方法,而且多次启动新线程,可能是导致复杂度上升的原因.
时序图
第二次作业
设计思路与感想
本次作业是多部电梯之间协同工作,且设计了容量问题,我是将上次的作业进行了优化,将上次作业中的调度器和电梯线程之间合并为了一个电梯线程,这样的话,多部电梯,就只用一个电梯类来刻画即可以达到目的,同时电梯内部的调度策略换为了LOOK,电梯间的调度策略是为每部电梯定义了一个函数:f(a,b,c,d,e,f);
- a表示电梯所在的楼层
- b表示电梯的运行状态上还是下
- c表示该请求所在的楼层
- d表示该请求要去往的楼层
- e表示电梯外已有的等待人数
- f表示电梯内已有的人数
- 函数返回一个值来衡量电梯的负载情况
主线程读到请求后,直接分配给f(a,b,c,d,e,f)值最小的电梯来运行.由于与上次的改动并不多,也就不会出现线程安全的问题,而且这样的调度策略在性能上也是比较好的一个动态调度方案.
类图
类图也比较简单,只有两个类,elevator类结合了上次作业的捎带功能,main方法加入了一个电梯之间的分发问题.
度量分析
本次作业中最大值是getfd()方法,这个方法就是上文中提到的为了调度所定义的f(a,b,c,d,e,f) ,可能在实现上还是有面向过程的思想,导致该方法循环复杂度极高.
时序图
第三次作业
设计思路与感想
- 本次作业是多部多类型电梯之间的调度.
- elevator基本没有更改,只是增加了一些配置内容,这里是直接使用了type和if判断,这与面向对象的设计思想有不符的地方,但是为了各种安全性问题,还是决定不进行大改了
- 因为上一次的类数过少,有些类它的内部可能干的事情太多了,于是又抽象出了两个调度器类,不过这次它不再是一个线程了,而只是一个管理者.同类型电梯之间的调度方案和第二次作业相同,抽象出了Dispacher类.不同类之间采用先拆分为直达请求的方法,然后为电梯分配,于是又有了个DispacherTop类.从上到下进行管理
- 这次被hack出了线程安全问题,但是最后还是没有找到问题在哪,因为我是尽量减少共享对象的设计方案,所以可能是在增加电梯时的管理层级出现了问题.
类图
- 可以看到出现了跨层之间的调度,这个是因为请求拆分以后进入一个队列需要为电梯调用,那么拆分后的两条请求,第一条执行完毕需要告诉顶层的调度器去继续执行.
度量分析
- 类的复杂度比较高,原因在于我没有把请求队列,等待队列这些部分抽象出来,来降低耦合度.其实可以抽象出等待队列,来避免跨层之间的调度,而是都与等待队列来交互,实现解耦.
时序图
SOLID设计原则分析
- Single Responsibility Principle:单一职责原则:一个类应该只有一个发生变化的原因
- 本次作业尽量做到单一职责原则,但是有些地方因为拆分为类比较复杂,于是选择了耦合.
- Open Closed Principle:开闭原则:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭
- 在每次作业中都有重新设计类图的结构,所以类都是改变过的,显然没有遵循到开闭原则.
- Liskov Substitution Principle:里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
- 没有使用继承,在第三次作业中使用了type,所以也没有遵循这个规定.
- Law of Demeter:迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话
- 没有遵循到这个设计原则,也正因为没有使用第三方来发送信息,所以导致耦合度较高
- Interface Segregation Principle:接口隔离原则:
1.客户端不应该依赖它不需要的接口。
2.类间的依赖关系应该建立在最小的接口上。- 未使用接口,同继承,增加了耦合度.
- Dependence Inversion Principle:依赖倒置原则:
1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。- 第三次作业中,上层模块依赖了底层模块,违反了依赖倒置原则.且没有实现抽象增加了耦合度