第一次作业
目标:设计单部可稍带电梯
设计模式:生产者-消费者模式
算法:SSTF (选择最近的请求作为主请求,途中判断同方向捎带)
Controller:调度器,保存请求队列
private final CopyOnWriteArrayListwaitQueue;//请求队列 private boolean stop = false; // 停止标记 public synchronized void addCustomer(Customer customer);// 加入顾客 public synchronized Customer getMainCustomer(int currentFloor);// 获取主请求 public synchronized int getOne(int direction, int currentFloor);// 获取捎带请求,每次返回一个请求 public synchronized boolean outOne(int currentFloor, boolean hasOpen);// private void printIn(Customer one, int currentFloor); private void printOpen(int currentFloor); private void printOut(Customer one, int currentFloor);
Elevator:电梯类,向调度器获取请求。在电梯类中只保留电梯操作:开关门、变换楼层
private static long TIME_MOVE = 400; private static long TIME_DOOR = 200; private int currentFloor; private int goalFloor; private Controller controller; private int direction = 0; // >0 向上 <0 向下 private Customer mainCustomer; public Elevator(Controller controller); public void run(); private void goUp(); private void goDown(); private void open(); private void firstopen(); private void close();
Customer:乘客类,向调度器投放请求。
private int id; private int from; private int to; private int direction; private int status = 0; // 0表示在外,1表示在电梯里面 /* status设置目的:由于第一次作业中我将乘客的进出全部放到调度器中,所以需要在判断乘客是否在电梯中从而判断进出电梯操作 */ public Customer(int id, int from, int to); public int getID(); public int getFrom(); public int getTo(); public int getDirection(); public int getStatus(); public void setStatus(int status);
Request:抽象到请求对象,对上面讲解的status进行设置
private int from; private int to; private int direction; public Request(int from, int to, int direction); public boolean addRequest(Customer one, int currentFloor);
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.Controller() | 1 | 1 | 1 |
Controller.addCustomer(Customer) | 2 | 2 | 2 |
Controller.getMainCustomer(int) | 3 | 5 | 6 |
Controller.getOne(int,int) | 1 | 4 | 4 |
Controller.getThisFloorMain(int) | 3 | 4 | 4 |
Controller.isStop() | 1 | 1 | 1 |
Controller.iswaitQueueEmpty() | 1 | 1 | 1 |
Controller.outOne(int,boolean) | 5 | 6 | 7 |
Controller.printIn(Customer,int) | 1 | 1 | 1 |
Controller.printOpen(int) | 1 | 1 | 1 |
Controller.printOut(Customer,int) | 1 | 1 | 1 |
Customer.Customer(int,int,int) | 1 | 1 | 1 |
Customer.getDirection() | 1 | 1 | 1 |
Customer.getFrom() | 1 | 1 | 1 |
Customer.getID() | 1 | 1 | 1 |
Customer.getStatus() | 1 | 1 | 1 |
Customer.getTo() | 1 | 1 | 1 |
Customer.setStatus(int) | 1 | 1 | 1 |
Elevator.Elevator(Controller) | 1 | 1 | 1 |
Elevator.close() | 1 | 2 | 2 |
Elevator.firstopen() | 1 | 2 | 2 |
Elevator.goDown() | 1 | 2 | 2 |
Elevator.goUp() | 1 | 2 | 2 |
Elevator.open() | 1 | 3 | 9 |
Elevator.run() | 3 | 9 | 10 |
Input.Input(Controller) | 1 | 1 | 1 |
Input.run() | 3 | 4 | 4 |
MainClass.main(String[]) | 1 | 1 | 1 |
Request.Request(int,int,int) | 1 | 1 | 1 |
Request.addRequest(Customer,int) | 1 | 8 | 8 |
Request.getTo() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Controller | 2.36 | 26 | |
Customer | 1 | 7 | |
Elevator | 2.71 | 19 | |
Input | 2 | 4 | |
MainClass | 1 | 1 | |
Request | 2.33 | 7 |
各方法复杂度都较低,由于调度器对人员进出进行控制,存在可扩展性较弱问题。
时序图
bug分析
未发现bug
第二次作业
目标:设计多部可稍带电梯
新增需求
设计模式:生产者-消费者模式
算法:SSTF
迭代:增加电梯线程数量即可,设计为电梯自由竞争请求,满足条件就向调度器申请请求。对前面设计进行简单修改,将乘客进出放入电梯中。调度器(非线程类)开启n个电梯线程。
电梯内增加人数限制,在取请求时判断是否可以加入。
增加负层:在1层、-1层增加特判。
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.Controller() | 1 | 1 | 1 |
Controller.addCustomer(Customer) | 2 | 2 | 2 |
Controller.getMainCustomer(int) | 3 | 5 | 6 |
Controller.getOne(int,int,int) | 4 | 4 | 5 |
Controller.isStop() | 1 | 1 | 1 |
Controller.iswaitQueueEmpty() | 1 | 1 | 1 |
Customer.Customer(int,int,int) | 1 | 1 | 1 |
Customer.getDirection() | 2 | 1 | 2 |
Customer.getFrom() | 1 | 1 | 1 |
Customer.getID() | 1 | 1 | 1 |
Customer.getStatus() | 1 | 1 | 1 |
Customer.getTo() | 1 | 1 | 1 |
Customer.setStatus(int) | 1 | 1 | 1 |
Elevator.Elevator(Controller,String) | 1 | 1 | 1 |
Elevator.close() | 1 | 2 | 2 |
Elevator.firstopen() | 1 | 3 | 4 |
Elevator.goDown() | 1 | 2 | 3 |
Elevator.goUp() | 1 | 2 | 3 |
Elevator.isEmpty() | 1 | 1 | 1 |
Elevator.open() | 1 | 7 | 9 |
Elevator.outOne() | 3 | 3 | 3 |
Elevator.printIn(Customer) | 1 | 1 | 1 |
Elevator.printOpen() | 1 | 1 | 1 |
Elevator.printOut(Customer) | 1 | 1 | 1 |
Elevator.resetGoalFloor() | 1 | 6 | 6 |
Elevator.run() | 3 | 10 | 11 |
Input.Input(Controller) | 1 | 1 | 1 |
Input.run() | 3 | 5 | 5 |
MainClass.main(String[]) | 1 | 1 | 1 |
Request.Request(int,int,int) | 1 | 1 | 1 |
Request.addRequest(Customer,int) | 1 | 8 | 8 |
Request.getDirection() | 1 | 1 | 1 |
Request.getTo() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Controller | 2.33 | 14 | |
Customer | 1.14 | 8 | |
Elevator | 2.62 | 34 | |
Input | 2.5 | 5 | |
MainClass | 1 | 1 | |
Request | 2 | 8 |
除了Elevator.run()
复杂度较高,其余方法较为简洁。
时序图
bug分析:
强测未发现bug,但是在互测中发现比较严重的bug。我在电梯人数限制方面有错误,限制7人,但是我的电梯却因为firstopen方法忘记对人数增加,导致第8个人可以进入。
在互测中也发现同屋其它同学bug,我采取了纯随机方式进行手工测试,但是也取得了不错的效果。(可能因为性能分较低,同屋的小伙伴层次都差不多吧,,ԾㅂԾ,,)
第三次作业
目标:设计多部可稍带电梯
新增:1.电梯到达楼层限制,支持换乘机制 2.动态增加电梯线程,且电梯分为ABC三种
设计模式:Worker-Thread模式
算法:Look算法
迭代:与小伙伴交流后认为look算法不易出“饿死”情况,性能更优,决定更改算法。由于更改算法,我对我的调度器与电梯类的调度算法进行了修改;将请求切割成两份,在乘客类记录换乘信息,电梯类每次取请求直接取直达请求,并对是否需要换乘、是否进行了换乘进行标记。
Controller
private final CopyOnWriteArrayListwaitQueue; private CopyOnWriteArrayList elevatorPool; // 电梯线程池 private CopyOnWriteArrayList exchangeQueue; // 换乘的请求 private CopyOnWriteArrayList exchanging;// 正在换乘的乘客 private boolean stop = false; public Controller(); public boolean isStop(); public boolean isEmpty(); public void start();// 开启最初的三个电梯线程 public synchronized void addCustomer(Customer customer); public synchronized void addElevator(Elevator elevator);// 新增 public synchronized int getDirection(int currentFloor, int[] ableFloor);// 新增 判断look算法电梯运行的方向 public synchronized Customer getOne(int[] ableFloor, int direction, int currentFloor,int peopleInNum, int capacity); public synchronized void addExchange(Customer customer);// 新增 前半段结束后放入换乘队列 public synchronized void addExchanging(Customer customer);// 新增 正在换乘的队列 public synchronized void removeExchanging(Customer customer);// 新增 public synchronized boolean hasPassenger(int[] ableFloor, int currentFloor,int direction);// 新增 电梯里面空后判断在同方向是否还有可捎带对象 public boolean scanFloor(int floor, int[] ableFloor);// 新增 寻找是否在可以到达的楼层中
Customer
private int id; private int goalFrom; private int goalTo; private int exchangeFloor; private int direction; private boolean exchange = false; private boolean haveExchanged = false; public Customer(int id, int goalFrom, int goalTo); private void needExchange();// 新增 电梯换乘记录 // 其余getter & setter算法未展开
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.Controller() | 1 | 1 | 1 |
Controller.addCustomer(Customer) | 2 | 2 | 2 |
Controller.addElevator(Elevator) | 1 | 1 | 1 |
Controller.addExchange(Customer) | 1 | 1 | 1 |
Controller.addExchanging(Customer) | 1 | 1 | 1 |
Controller.getDirection(int,int[]) | 6 | 15 | 16 |
Controller.getOne(int[],int,int,int,int) | 8 | 12 | 13 |
Controller.hasPassenger(int[],int,int) | 1 | 20 | 26 |
Controller.isEmpty() | 1 | 3 | 3 |
Controller.isStop() | 1 | 1 | 1 |
Controller.isexchangeQueueEmpty() | 1 | 1 | 1 |
Controller.isexchanging() | 1 | 1 | 1 |
Controller.iswaitQueueEmpty() | 1 | 1 | 1 |
Controller.removeExchanging(Customer) | 1 | 1 | 1 |
Controller.scanFloor(int,int[]) | 3 | 1 | 3 |
Controller.start() | 1 | 2 | 2 |
Customer.Customer(int,int,int) | 1 | 1 | 2 |
Customer.getDirection() | 3 | 1 | 5 |
Customer.getExchangeFloor() | 1 | 1 | 1 |
Customer.getGoalFrom() | 1 | 1 | 1 |
Customer.getGoalTo() | 1 | 1 | 3 |
Customer.getId() | 1 | 1 | 1 |
Customer.isExchange() | 1 | 1 | 1 |
Customer.isHaveExchanged() | 1 | 1 | 1 |
Customer.needExchange() | 1 | 1 | 33 |
Customer.setHaveExchanged(boolean) | 1 | 1 | 1 |
Elevator.Elevator(Controller,String,String) | 2 | 2 | 5 |
Elevator.close() | 1 | 2 | 2 |
Elevator.firstOpen() | 2 | 7 | 9 |
Elevator.goDown() | 1 | 2 | 3 |
Elevator.goUp() | 1 | 2 | 3 |
Elevator.isEmpty() | 1 | 1 | 1 |
Elevator.open() | 1 | 11 | 13 |
Elevator.outOne() | 3 | 3 | 3 |
Elevator.printIn(Customer) | 1 | 1 | 1 |
Elevator.printOpen() | 1 | 1 | 1 |
Elevator.printOut(Customer) | 1 | 1 | 1 |
Elevator.run() | 6 | 17 | 19 |
Input.Input(Controller) | 1 | 1 | 1 |
Input.run() | 3 | 6 | 6 |
MainClass.main(String[]) | 1 | 1 | 1 |
SafeOurPut.println(String) | 1 | 1 | 1 |
Class | OCavg | WMC | |
Controller | 3.31 | 53 | |
Customer | 2.9 | 29 | |
Elevator | 3.75 | 45 | |
Input | 3 | 6 | |
MainClass | 1 | 1 | |
SafeOurPut | 1 | 1 |
由分析可见:我本次作业中Controller.getDirection(int,int[])
、Controller.getOne(int[],int,int,int,int)
、Controller.hasPassenger(int[],int,int)
负责度较高,因为这几个方法不仅遍历了请求队列,而且每次都需判断楼层,导致复杂度较高。
1.分析换乘策略,可以发现楼层特点,分为电梯运行方向进行分析。可以直达则不标记换乘位,如过不可直达怎在1层或15层换乘。其中可以发现3层比较特殊,会发生折返情况。分别在1层和5层发生换乘。
2.look算法:放弃主请求思想,改为电梯状态即电梯运行方向分析。
当方向为0即暂停,则寻找下一请求,若请求进入楼层高于当前楼层,则向上;若低于,则向下;若恰好相等,则以该请求运行方向作为电梯运行状态方向。
当方向为1或-1即向上或向下,如若电梯内的等待队列不空,则继续执行;若已经空了,但同方向还有可能捎带请求,继续保持原方向;若已经没有请求,则设置方向为0,进行上面一步的判断。
3.解决cpu超时问题:本次作业需要注意,仅仅判断请求队列是否为空、是否还有换乘正在进行,还需要注意在队列不为空的时候,由于电梯到达楼层有限,很多电梯也无法获取请求,处于空转状态。所以每次需要判断是否本电梯可稍带,调用wait()。
时序图
bug分析
本次作业可以说是我最难过的一次,强测有6个点因为换乘策略有一种情况不对,导致出现线程无法结束的情况。可以说非常不应该,我对此也难过了很久(不得不承认我是个脆弱的女孩子/(ㄒoㄒ~)/)其余通过的点的性能分基本满了....(不过又有什么用呢/(ㄒoㄒ)/)
互测本姑娘一直采取人不犯我,我不犯人的策略,但是这次是对方在最后2小时对我出击,导致我一直以为自己并没有被hack。因此并没有对他人回刀。唉....不提了....睡了睡了.....(~﹃~)~zZ
SOLID分析
SRP:本次作业每个类都有明确的职责,乘客类决定换乘、电梯线程进行请求的处理、调度器进行接受请求和提供合理请求、输入线程向调度器输入请求
OCP:除调度器和电梯外其它类很好满足了开闭原则。但是三次作业的迭代开发可见,调度器和电梯每次增加新功能,都要进入电梯和调度器进行open改动。
LSP:由于本次作业我并没有设计除线程继承以外的继承关系,所以符合LSP规则,未对父类进行引用
ISP:未设计接口,程序可扩展性还是较差。改进方案可以是每一类电梯单独享有一个调度器,继承自主调度器。如此每新增一种电梯类型,并无需对主调度器进行open
DIP:未设计抽象类。
扩展性分析
通过上文分析可见,我是一个功利girl。本次作业更多的是以完成为目标,但是忽略了很多课上讲到的设计模式原则。在大工程方面达不到很好的扩展性。
改进方案
每一类电梯单独享有一个调度器,继承自主调度器。如此每新增一种电梯类型,并无需对主调度器进行open,直接增加调度器类型。
为电梯做一个接口,每类电梯都继承自这个Elevator
接口。定义电梯的上升下降、开关门以及乘客进出。
为乘客做一个抽象类,乘客可分为换乘与不换乘,以及可能存在的愿意走楼梯的类型。
心得体会
这三次作业对我来讲最痛苦的莫过于第一次作业,反复出现死锁,对多线程理解困难。但是突破这个瓶颈后,这三次作业就不难向下推进。但是令我最心痛的是第三次作业,错的实在是不应该,还是太过相信中测,以后过了中测也要多测试。
在完成作业的过程中,我并没有很好的注意到可扩展性的设计。为了便于完成,我还是采取了“面向完成”的设计方法,用了比较直白的面向对象思想。但是完成后再进行反思,可以看到还有很多值得改进的地方。
不要太相信中测!不要太相信中测!不要太相信中测!