一、作業架構設計
-
第一次作業
作業需求:
實作一個UML類圖解析器,可以通過輸入各種指令來進行類圖有關資訊的查詢。
難點:
CLASS_ASSO_COUNT,CLASS_TOP_BASE,CLASS_IMPLEMENT_INTERFACE_LIST
架構設計:
類圖中的元素有明顯的層次性,根據層次重寫三個類,對應Class,Interface,Operation。
MyClass屬性,MyInterface類似。
public class MyClass extends MyElement implements MyObject {
private HashMap<String, MyOperation> idOpMap = new HashMap<>(); //Operation id->MyOperation
private HashMap<String, HashSet<MyOperation>> nameOpMap = new HashMap<>(); //Operation name->MyOperation
private HashMap<String, UmlAttribute> idAttriMap = new HashMap<>(); //Attribute id->UmlAttribute
private HashMap<String, HashSet<UmlAttribute>> nameAttriMap = new HashMap<>(); //Attribute name->UmlAttribute
private MyClass father = null; //Father MyClass
private HashSet<MyInterface> interfaceRealizationSet = new HashSet<>(); //Interface MyInterfaceprivate HashSet<MyClass> assEndSet = new HashSet<>(); //AssociationEnd MyClass
}
MyOperation屬性
public class MyOperation extends MyElement {
private Visibility visibility; //Visibility
private HashMap<String, UmlParameter> idParaMap = new HashMap<>(); //parameter(in)
private UmlParameter returnp = null; //parameter(return)
}
實作思路:
-
- 由于不保證元素出現的順序,先周遊元素并分類。相當于給每個uml元素準備一個口袋,按照類别放到各自的口袋。
- 周遊Class和Interface對應的口袋,生成對象。這兩種對象類似一個容器,通過add方法,添加自己的方法,屬性,或者關聯。
- 再周遊Operation,Attribute,Association,Generalization,Realization,添加到對應容器。
- 實作查詢指令,這裡隻探讨剛才提出的三個難點。他們有一個相似的解決思路——dfs。比如CLASS_ASSO_COUNT的計數函數,先調用父類的函數,傳回父類Association的集合,再自己的Association合并。CLASS_IMPLEMENT_INTERFACE_LIST指令應該注意,既要有父類實作的接口,又要有自己的接口繼承的父接口。
- 應用緩存思想:把查詢過的結果儲存好,接到指令先判斷是不是有存儲的結果。
-
第二次作業
-
- 擴充狀态圖和順序圖的查詢指令。
- 對類圖進行模型有效性檢查。
檢查循環繼承,檢查重複繼承;查詢一個狀态的後繼。
有四個功能相對獨立的接口:
UmlClassModelInteraction
UmlCollaborationInteraction
UmlStateChartInteraction
UmlStandardPreCheck把方法實作在一個類裡面代碼會超長,功能也會十分混亂。應該寫四個單獨的類來實作方法,在主類裡複用這些方法。代碼複用有兩個途徑,一是繼承,二是在類裡建立對象,我選擇了後者。
狀态圖和順序圖采取了和類圖相似的層次性架構。我還寫了一個有向圖的類(Graph),封裝求解可達節點的算法。這是狀态圖的結構。
- 循環繼承:類是節點,繼承關系是有向邊,從任意一個類開始dfs,記錄經過的路徑,把路徑傳入更深一層的遞歸。如果新遇到的類已經在路徑裡出現過,說明在這條路徑上,從這個類上一次出現到這一次出現之間的節點形成一個環,把環上的節點加入一個集合。從剩下類中挑出一個不在該集合内的類,繼續dfs,直到所有類要麼周遊過,要麼出現在集合裡。集合裡就是循環繼承的類。
- 重複繼承:接口重複繼承接口,對于一個接口,先dfs判斷所有的父接口如果有重複繼承,那這個接口也重複繼承;再判斷父接口繼承的接口集合之間有沒有交集,有交集則重複繼承。類重複實作接口,對一個類,先dfs判斷父類如果重複實作,或接口重複繼承,那這個接口也重複繼承;再判斷父類實作的接口集合和接口繼承的接口集合之間有沒有交集,有交集則重複繼承。
二、四個單元中架構設計的演進
-
第一單元:帶三角函數嵌套的表達式求導。
我的架構是給每一種運算寫一個類,回看代碼有很多面向過程的寫法,幾個運算類之間的聯系比較混亂。代碼複用性很不好,後兩次作業全部重構了。第三次作業難度激增,由于對嵌套運算的不合理遞歸和for循環,很多點都逾時了。這是因為我過于看重架構實作,忽略了算法選擇,按照自己的方法一寫到底。
-
第二單元:多線程電梯排程。
這個單元我認識了幾種設計模式:生産消費者模式,工廠模式,單例模式。電梯問題中的排程器是單例模式,考慮的是代碼安全性;三個主要的類構成生産消費者模式,電梯是消費者,請求輸入是生産者,排程器進行請求配置設定。第一次的配置設定算法用了傻瓜電梯的先到先服務方法,第二三次是主請求的捎帶方法,第三次還需要考慮換乘問題。我前兩次作業的複用性很好,第三次作業寫的飛快,正确性也有保障。我總結出提高前期代碼拓展性的方法是,架構設計越清晰越好,函數職責越簡單越好,線程的互動越少越好。比如電梯的第二次作業,所有配置設定任務都是排程器完成的,電梯隻需要上下樓開關門,并從排程器内部的一個隊列裡取出第一個的請求。第三次作業增加到三部電梯,變化隻在排程器現在需要維護三個請求隊列,三架電梯從各自隊列取請求,電梯類不用修改。
-
第三單元:JML規格。
JML相比自然語言優越在其簡潔性,邏輯性和無二義性。熟練掌握JML是第三單元的學習目标。
這次作業的類也是階層化的,節點和邊是最底層的類,上面一層是Path類,最上面的RailwaySystem類。另外封裝一個計算最短路徑的算法類。三次作業的差別就是圖該怎麼建的問題,第三次引入換乘問題,我使用了拆點法。如果資料結構忘的差不多了,這個單元就該還債了。最短路徑算法有dijkstra和folyd可選,我三次作業一直沿用了dij算法,沒有堆優化的dij是沒有靈魂的dij,寫着沒有靈魂的dij我幾乎T了第三次強測所有點。分析算法的複雜度應該成為我的一個習慣,不能隻滿足于通過中測,應該持續探索優化的空間。
-
第四單元:解析UML圖。
這次我了解源碼和指導書花費了大量時間,代碼實作起來反而簡單,類的結構是高度階層化的。頂層類一級級調用底層類的函數,完成一個查詢功能。在思考了幾種算法的可行性之後,仍然選擇了最開始想到的dfs。這兩個單元的作業複習了圖結構的很多經典算法,包括最短路徑,可達節點,尋找環路,尋找根節點。
我注意到一個規律,對原始資料的預處理方式,嚴重影響後續實作的複雜程度。第三次作業搭建地鐵路線的時候,如果每新加一條路徑,都添加到現有的圖上會很複雜,重建立圖反而簡單,時間也不會耽誤太多。第四次作業對于所有UML元素,第一次周遊先将元素分類,再逐類處理更簡單。
三、四個單元中測試了解的演進
從構造方法分類,分為人工構造測試集和自動生成測試集。從測試目的分類,分為正确性測試和壓力測試。
第一次作業是表達式求導,适合人工構造和自動生成相結合。先人工構造一些特殊的點,比如WRONG FORMAT的個例。然後自動生成複雜嵌套的測試點,測試求導的正确性。構造極端資料點進行壓力測試也是必要的,可以揪出爆棧問題。
第二次作業是多線程電梯,最适合對拍,用随機生成的一些電梯請求測試。
第三次作業是JML規格,認識了JUnit插件,可以自動生成一些邊界條件上的測試用例。其他測試方法無法相比的一個優點是,JUnit可以針對每一個方法測試,這樣發生了錯誤更好定位。TLE是這次作業最大的問題,構造壓力測試很重要。
第四次作業是UML圖查詢,需要用staruml畫圖生成測試資料。畫圖要考慮全面所有情況,比如循環繼承的繼承環應該有所交疊,單繼承多繼承都出現。
四、總結自己的課程收獲
- 從面向過程轉變成面向對象。
- 獲得了完成千行級代碼的工程能力。
- 寫出有拓展性的代碼,友善功能拓展。
- 用插件分析代碼複雜度。
- 自動生成測試用例。
- JML規格描述功能實作。
- UML圖整理架構思路。
- 尋找算法資料,優化設計的能力。
五、改進建議
- 理論課增加JAVA易混淆知識點的講解,少一些工程思想的介紹。十二次寫代碼作業把我們的實踐能力練習的很充分,但知識上有很多漏洞。類繼承和接口實作有很多繁瑣的知識點我們始終沒有觸及,我在幾次作業中應用到的繼承也是特别簡單的繼承結構。有時候架構不好也不是思考的少,而是從練習中學習的這種教育模式,讓我們隻有用到了才去查,淺嘗辄止,基礎沒有鞏固,不如老師提前點播一下。
- 上機課的題目應該公布出來,有些課上沒有完成的同學,之後還能繼續研究。感覺我每次上機都是慌裡慌張,從來沒有時間好好研究一下上機的那些問題。上機過去就過去了,也沒有條件細想,收獲很少。
- 在每個單元的第一次作業時就公布三次作業的大緻需求。我完成第一次作業時,不知道該怎麼設計架構才能友善後面的拓展,因為不知道還要增加哪些功能,提高拓展性就是盲猜。有時候寫一個性能最優但實作複雜的算法,反而是最不好拓展的。