北航OO(2020)第二單元博客作業 [TOC] 設計策略分析(多線程視角) 本單元的三次作業中,我採用了相似的策略:採用輸入線程與電梯線程通過線程安全的調度器進行交互的方式。這種方式基本屬於生產者 消費者模式。在調度器的設計方面,我主要採用synchronized關鍵字結合wait和notify方 ...
北航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原則及一些其他重要的設計原則也是十分重要的,這些原則保證了程式結構的清晰性和良好的可擴展性。在本單元作業中,部分設計原則未能體現甚至有所違背,在今後的編程中會多加註意。