2020北航面向对象第二单元总结
设计策略分析
在本单元中的三次作业均采取了相同的策略,架构基本相同,采用生产者-消费者模式,只是在电梯的具体运行和任务分配上略有不同。核心类为Request, Mission, Elevator。 Request负责接受分解请求并加入到Mission的任务序列中,是作为生产者的单个线程。 Mission则是作为托盘存在的,接受请求,并在电梯需要任务时找到合适的任务交给电梯,本身是共享资源,并不作为线程出现,其内部所有方法均上锁,保证在同一时间内生产者和多个消费者只有一个线程能对任务序列进行写操作。 Elevator是运送乘客的电梯,根据电梯数量的不同可能新建多个电梯线程,电梯在请求任务时会将Mission中的任务删除,然后加入自己的任务列当中,实现各个电梯互相独立,任务类也只要在分配任务时知道电梯的状态即可。整体的捎带采用ALS算法,但选择最近的乘客作为主任务,协同控制问题基本不出现,因为在Mission类对所有方法进行了加锁,可能存在冗余但占比很少。
第三次作业的架构设计分析
可扩展性:具有较好的扩展性,因为类之间只有任务的交互,其他类型的数据都为只读,因此耦合度较小,每一类的职责较为明确,但是由于电梯并没有用单例模式、继承和工厂模式实现,因此在新增电梯时可能需要修改电梯类而不是简单等新增子类,这一点是有待改进的。
SRP原则:基本符合
- Request的职责单一明确,在三次的作业中基本没有太大的修改,只有适应输入的一点改动。
- Mission作为托盘存放着电梯尚未接受的任务,并且提供了调度策略以供电梯接受任务时使用。
- Elevator执行自己的内部任务,并在适当的时机到Mission处请求任务
OCP原则:略有不符
电梯应当通过继承实现新增电梯,但本单元作业没有使用因此导致每次需要修改电梯类的属性,导致违背开闭原则
LSP&ISP&DIP原则:本单元作业未使用Thread以外的接口或继承因此无从体现上述原则
基于度量的程序结构分析
homework1&homework2
第二次作业相比第一次作业只修改了电梯的可达层数然后根据需求新建了电梯线程,没有新增的方法或功能,因此二者极为相似,在度量阶段统一分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.Close() | 1 | 3 | 3 |
Elevator.Elevator(Mission) | 1 | 1 | 1 |
Elevator.Open() | 1 | 2 | 2 |
Elevator.completeRequest() | 1 | 3 | 3 |
Elevator.mainRequestGet() | 3 | 2 | 4 |
Elevator.minorRequestGet() | 1 | 2 | 2 |
Elevator.moveTo(int) | 1 | 2 | 3 |
Elevator.passengerIn(PersonRequest) | 1 | 1 | 1 |
Elevator.passengerOut(PersonRequest) | 1 | 1 | 1 |
Elevator.run() | 3 | 2 | 3 |
Main.main(String[]) | 1 | 1 | 1 |
Mission.get(int,ArrayList ) | 3 | 11 | 12 |
Mission.put(PersonRequest) | 1 | 1 | 1 |
Mission.setEnd() | 1 | 1 | 1 |
Request.Request(Mission) | 1 | 1 | 1 |
Request.run() | 3 | 4 | 4 |
Class | OCavg | WMC |
---|---|---|
Elevator | 2.1 | 21 |
Main | 1 | 1 |
Mission | 3.33 | 10 |
Request | 2 | 4 |
在前两次的作业中为了防止线程安全出现问题,我的调度策略是直接写入了Mission的get方法中,因此导致其复杂度很高,这点需要改正,在第三次作业当中也有了一些改善,其他类的复杂度较低并且耦合度也并不是很高,相比第一单元有了较大的进步
homework3
本次作业当中存在楼层判断较多,复杂度有所提高
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.Close() | 1 | 3 | 3 |
Elevator.Elevator(Mission,String,String) | 1 | 1 | 1 |
Elevator.Open() | 1 | 2 | 2 |
Elevator.completeRequest() | 2 | 5 | 24 |
Elevator.mainRequestGet() | 3 | 2 | 4 |
Elevator.minorRequestGet() | 1 | 2 | 2 |
Elevator.moveTo(int) | 3 | 5 | 14 |
Elevator.passengerIn(PersonRequest) | 1 | 1 | 1 |
Elevator.passengerOut(PersonRequest) | 1 | 1 | 1 |
Elevator.run() | 4 | 3 | 12 |
Main.main(String[]) | 1 | 2 | 2 |
Mission.finish() | 1 | 1 | 1 |
Mission.get(int,ArrayList ,String) | 7 | 10 | 38 |
Mission.giveMinor(int,ArrayList ,String) | 4 | 7 | 27 |
Mission.put(PersonRequest) | 1 | 1 | 1 |
Mission.setEnd() | 1 | 1 | 1 |
Producer.Producer(Mission) | 1 | 1 | 1 |
Producer.eleProduce(ElevatorRequest) | 1 | 1 | 1 |
Producer.run() | 3 | 5 | 5 |
Class | OCavg | WMC |
---|---|---|
Elevator | 4 | 40 |
Main | 2 | 2 |
Mission | 4.2 | 21 |
Producer | 2 | 6 |
本次在电梯运动时有楼层判断,在完成任务时有捎带判断,在接取任务时也有与上述类似的判断,在构建代码时我直接将这些判断写入了相应的方法当中,导致部分方法的复杂度大幅上升,并且代码中存在很多的重复部分因为判断多数都很相似,在这次的作业中我应当将判断的部分建立一个枚举类,这样也能够更好的实现开闭原则。在这三次作业中暴露在外面的方法相较于第一单元少了很多,并且基本都是需要暴露的方法,这一点也应当保持
时序分析
这三次作业在结构上没有大的区别,没有新建类,也没有改变类的关系,因此时序图没有太大区别,仅涉及到线程类的创建,因为线程类本身只会执行自己的run方法,并于非线程类Mission进行交互,因此时序图只有如下create的交互
Bug分析
第一次作业的bug主要原因是对于线程的不理解以及锁的误用,在熟悉了多线程相关知识后,第一次作业没有出现相关bug
第二次作业在第一次作业的基础上没有多数修改,但在自行测试很容易就发现了一个bug,我的电梯在运行时会逐渐减少,原因就是在判断托盘为空就wait的时候没有使用while而是使用的if,因为第一次作业只有一部电梯,if不会产生问题,但是在第二次作业就出现了问题,改正了这一点之后也顺利通过了第二次作业
第三次作业实质上只要在判断楼层和换乘时仔细一些,不要漏掉情况就可以了,因为与前两次架构相同,采用ALS,因此很大概率不会出现ctle或是rtle类的bug,最后也是顺利通过了测试,三次都拿到还算不错的成绩
互测策略
很遗憾本人在本单元并未构建评测机,因此在互测时并没有构造很多的有效数据,对于多线程的理解也不是很透彻,因此在互测阶段只是上交了一些自己产生了问题的测试样例,在第二次作业侥幸hack成功一次
心得体会
在经过本单元的作业以后,我从对多线程一无所知到已经可以初步实现一个多线程程序,在知识的学习方面有较大的收获。这一单元相比于第一单元的两次重构实现了很大程度的进步,后两次的作业都是迭代实现,并且对于类的架构、功能都没有进行改动或者破坏,实实在在体会到了面向对象的编程思想以及它带来的优势,本单元的作业在第一次花费了较多的时间,第二次和第三次完成作业都没有耗费太长时间,多数时间都用在了对于算法的调整上面,但是在第三次作业的时候会想着“反正是最后一次作业了,就不用考虑迭代了”,这种想法是很不好的也导致了我的架构在第三次复杂性显著提高,在之后的作业我要做到善始善终,这门课程是用来学习并且锻炼能力的,一味的看重分数只会失去的更多,在之后的作业会把握住本单元讲述的5个原则,争取写出更加符合面向对象思想的代码。