北航OO(2020)第二单元博客作业
设计策略分析(多线程视角)
本单元的三次作业中,我采用了相似的策略:采用输入线程与电梯线程通过线程安全的调度器进行交互的方式。这种方式基本属于生产者-消费者模式。在调度器的设计方面,我主要采用synchronized关键字结合wait和notify方法完成互斥访问和同步控制。
Homework 3 SOLID分析与可扩展性分析
Single Responsibility Principle
Elevator类有且仅有一个public方法:run方法,仅仅负责执行电梯的运行逻辑。
Main类有且仅有一个public方法:main方法。但main方法既负责创建线程,又负责输入的处理与结束,具有多重责任。可以采取创建新的输入类和输入线程的方法解决这个设计问题。
Dispatcher类具有多个public方法,但每个方法都有唯一确定的职责,通过下面章节中UML类图即可确认这一特性。Dispatcher类负责将请求放置于每一楼层,供Elevator取用,满足了SRP原则。
FloorConverter类有两个public方法,负责将楼层号与数组下标相互转换,满足了SRP原则。
FloorSelector类有两个public方法,分别用来判断当前层是否可以停靠和选择乘客的目的楼层。这两个职责都需要电梯的停靠信息,而且逻辑联系较为紧密,因此可以置于同一类中完成。
Open Close Principle
本次作业中除继承Thread类外没有使用任何继承,几乎都是通过修改已有实现完成新增功能,违反了OCP原则。
Liskov Substitution Principle
本次作业中除继承Thread类外没有使用任何继承,因此该原则无从体现。
Interface Segregation Principle
本次作业中没有使用任何接口,因此该原则无从体现。
Dependency Inversion Principle
本次作业中除继承Thread类外没有使用任何继承,且没有使用任何接口,因此该原则无从体现。
可扩展性
通过上面的分析可以看出,扩展功能几乎一定需要通过改写已有的实现来完成。但由于类的public方法职责都较为明确,这样的设计可以为功能的扩展带来一定的便利。
基于度量的程序结构分析
Homework 1
代码度量
Type Name | Method Name | LOC | CC | PC |
---|---|---|---|---|
Dispatcher | Dispatcher | 10 | 2 | 0 |
Dispatcher | setFinished | 6 | 1 | 0 |
Dispatcher | addRequest | 13 | 2 | 1 |
Dispatcher | getRequests | 14 | 2 | 2 |
Dispatcher | getTask | 30 | 9 | 1 |
Elevator | Elevator | 11 | 2 | 1 |
Elevator | run | 39 | 10 | 0 |
Elevator | stopOnFloor | 20 | 2 | 1 |
Elevator | getOn | 7 | 2 | 1 |
Elevator | getOff | 7 | 2 | 0 |
Elevator | go | 10 | 1 | 0 |
Main | main | 23 | 3 | 1 |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC | NC | DIT | LCOM | FANIN | FANOUT |
---|---|---|---|---|---|---|---|---|---|---|---|
Dispatcher | 5 | 1 | 5 | 5 | 80 | 16 | 0 | 0 | 0.0 | 2 | 0 |
Elevator | 6 | 0 | 6 | 2 | 102 | 19 | 0 | 0 | 0.0 | 1 | 1 |
Main | 0 | 0 | 1 | 1 | 25 | 3 | 0 | 0 | -1.0 | 0 | 2 |
类图
本次作业构建了三个类。这些类的封装较好,对外暴露的方法较少,且都具有明确的职责。大部分方法具有明确的职责,也较为简洁。但是,Elevator类的run方法展开了电梯的一次运行逻辑,整体较为复杂;Dispatcher类的getTask方法也较为复杂,不便改动和维护。
Homework 2
代码度量
Type Name | Method Name | LOC | CC | PC |
---|---|---|---|---|
Dispatcher | Dispatcher | 10 | 2 | 0 |
Dispatcher | setFinished | 6 | 1 | 0 |
Dispatcher | addRequest | 13 | 2 | 1 |
Dispatcher | getRequests | 23 | 6 | 3 |
Dispatcher | getTask | 35 | 9 | 1 |
Dispatcher | getUpperTask | 8 | 3 | 1 |
Dispatcher | getLowerTask | 8 | 3 | 1 |
Elevator | Elevator | 12 | 2 | 2 |
Elevator | run | 39 | 10 | 0 |
Elevator | stopOnFloor | 20 | 2 | 1 |
Elevator | getOn | 7 | 2 | 1 |
Elevator | getOff | 7 | 2 | 0 |
Elevator | go | 10 | 1 | 0 |
FloorConverter | indexToFloor | 7 | 2 | 1 |
FloorConverter | floorToIndex | 7 | 2 | 1 |
Main | main | 27 | 4 | 1 |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC | NC | DIT | LCOM | FANIN | FANOUT |
---|---|---|---|---|---|---|---|---|---|---|---|
Dispatcher | 6 | 1 | 7 | 5 | 111 | 26 | 0 | 0 | 0.0 | 2 | 1 |
Elevator | 8 | 0 | 6 | 2 | 105 | 19 | 0 | 0 | 0.0 | 1 | 2 |
FloorConverter | 0 | 0 | 2 | 2 | 16 | 4 | 0 | 0 | -1.0 | 2 | 0 |
Main | 0 | 0 | 1 | 1 | 29 | 4 | 0 | 0 | -1.0 | 0 | 2 |
类图
本次作业与上一次作业架构极为相似,只是多了FloorConverter类。因此优缺点与上次大体相同,在此不再赘述。
Homework 3
代码度量
Type Name | Method Name | LOC | CC | PC |
---|---|---|---|---|
Dispatcher | Dispatcher | 10 | 2 | 0 |
Dispatcher | setFinished | 8 | 2 | 0 |
Dispatcher | addRequest | 13 | 2 | 1 |
Dispatcher | elevatorAddRequest | 9 | 2 | 2 |
Dispatcher | getRequests | 12 | 2 | 4 |
Dispatcher | filterRequests | 19 | 4 | 5 |
Dispatcher | decreaseRequestCount | 6 | 2 | 0 |
Dispatcher | getTask | 35 | 9 | 2 |
Dispatcher | hasValidRequest | 10 | 3 | 4 |
Dispatcher | getUpperTask | 11 | 4 | 2 |
Dispatcher | getLowerTask | 11 | 4 | 2 |
Elevator | Elevator | 28 | 5 | 3 |
Elevator | run | 40 | 10 | 0 |
Elevator | changeDirection | 8 | 2 | 0 |
Elevator | stopOnFloor | 18 | 2 | 1 |
Elevator | getOn | 10 | 2 | 1 |
Elevator | getOff | 15 | 3 | 0 |
Elevator | go | 12 | 1 | 0 |
FloorConverter | indexToFloor | 7 | 2 | 1 |
FloorConverter | floorToIndex | 7 | 2 | 1 |
FloorSelector | isStoppable | 12 | 4 | 2 |
FloorSelector | selectFloor | 15 | 5 | 4 |
FloorSelector | selectFloorA | 6 | 2 | 1 |
FloorSelector | selectFloorB | 18 | 6 | 3 |
FloorSelector | selectFloorC | 24 | 8 | 3 |
Main | main | 33 | 6 | 1 |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC | NC | DIT | LCOM | FANIN | FANOUT |
---|---|---|---|---|---|---|---|---|---|---|---|
Dispatcher | 6 | 1 | 11 | 7 | 152 | 36 | 0 | 0 | 0.2727272727272727 | 2 | 2 |
Elevator | 10 | 0 | 7 | 2 | 143 | 25 | 0 | 0 | 0.0 | 0 | 3 |
FloorConverter | 0 | 0 | 2 | 2 | 16 | 4 | 0 | 0 | -1.0 | 3 | 0 |
FloorSelector | 3 | 0 | 5 | 2 | 80 | 25 | 0 | 0 | 1.0 | 2 | 1 |
Main | 0 | 0 | 1 | 1 | 35 | 6 | 0 | 0 | -1.0 | 0 | 1 |
类图
本次作业构建了五个类。这些类的封装较好,对外暴露的方法较少,且都具有明确的职责,类间的协作关系也较为明确。大部分方法具有明确的职责,也较为简洁。但是,与前两次作业一样,Elevator类的run方法仍然展开了电梯的一次运行逻辑,整体较为复杂;Dispatcher类的getTask方法也较为复杂,不便改动和维护。
UML时序图
由于三次作业的线程交互模式较为类似,因此统一绘制UML时序图如下。
Bug分析
本单元作业在公测和互测中未出现任何bug。
在第三次作业的开发过程中,由于Dispatcher类的getTask方法和getRequests方法判断请求是否为空的标准不一致,我的电梯线程在一些情况下出现了轮询,导致在中测中出现了CTLE的现象。我在本地通过在JProfiler中观察线程状态及CPU时间,并在程序中打印log的方式,最终定位了bug的位置,并进行了修复。
Hack策略分析
本次作业同第一单元不同,需要做到在线交互。因此,本次作业的测试要求更高。但是,由于摸鱼心切,我仍然采用了手动构造测试用例的方法。因此,本单元我未能发现他人的任何bug。
心得体会:线程安全与设计原则
通过本单元的三次作业,我对Java多线程编程有了一个初步的认识,并了解了一些简单的互斥访问与同步控制的方法。在多线程编程中,通过线程安全的共享对象来完成线程间交互是十分清晰而简洁的方式。通过对象锁,可以使该对象在同一同步块内只能被一个线程访问,且不会被打断。再结合wait和notifyAll方法,可以避免轮询,高效利用CPU资源。此外,在设计中遵循SOLID原则及一些其他重要的设计原则也是十分重要的,这些原则保证了程序结构的清晰性和良好的可扩展性。在本单元作业中,部分设计原则未能体现甚至有所违背,在今后的编程中会多加注意。