目錄java
第一次做業比較簡單,僅包含類圖的解析。正確理解UML元素的含義,以及每種UMLElement
的各個屬性的所指向的東西,就能比較容易地完成這次做業。算法
這裏我構建了兩個類:ClassRelations
以及InterfaceRelations
用來存儲類和接口所包含的屬性、方法等類圖的基本信息,同時還要保存父類(父接口)、實現的接口,用來遞歸查找。是一種比較容易想到的實現方式。編程
類圖以下:設計模式
爲了加快對ClassName
的查找和判斷,使用了一個鄰接表來存儲同名的類,方便查找相應的類。安全
private HashMap<String, ArrayList<UmlClass>> classNameMap = new HashMap<>(); private void checkClassName(String className) throws ClassNotFoundException, ClassDuplicatedException { if (!classNameMap.containsKey(className)) { throw new ClassNotFoundException(className); } else if (classNameMap.get(className).size() > 1) { throw new ClassDuplicatedException(className); } }
第二次做業在第一次做業的基礎上,增長了對UML狀態圖和UML時序圖的解析。此外,還增長了三個模型有效性檢查的規則,分別是:不含重名的成員、不能循環繼承、不能重複實現接口。bash
與第一次做業相比,總體的架構變化不大,總的思想仍然是把每一種UML元素放到對應的UML圖中,根據需求對這些數據加以維護。總體稍加改動後直接繼承上次的做業。多線程
本次實現的MyUmlGeneralInteraction
直接繼承了上次的MyUmlInteraction
。對於狀態圖和時序圖,與上次做業相似的,我建立了兩個類StateMachineRelations
和SequenceRelations
用來保存與狀態圖和時序圖相關的信息。架構
類圖以下:併發
對於模型有效性的檢驗編程語言
R001:針對下面給定的模型元素容器,不能含有重名的成員(UML002)
與檢測類名重複相似的,在ClassRelations
類中,添加屬性和關聯對端的時候,對同名的屬性和關聯對端進行計數,就能夠得到找到重名的成員。
R002:不能有循環繼承(UML008)
R003:任何一個類或接口不能重複繼承另一個接口(UML009)
這兩個規則都是和繼承或接口的實現相關的,因此放在一塊兒處理。繼承/實現關係構成了一個有向圖,循環繼承和屢次實現接口的優先圖,具備以下特色:
因此,將邊權定義爲優先圖節點之間的路徑數目,那麼也就能同時判斷是否存在循環繼承和重複繼承
對於節點i
到節點j
的路徑數目,對於中間節點k
,存在以下的關係:route(i, j) = route(i, j) + route(i, k) * route(k, j)
(貌似是動態規劃的狀態轉移?有沒有算法大佬能夠講一下,沒學過不是很懂。順便期待下學期算法有一個好的收穫)
使用UmlClassOrInterface
接口對類和接口的關係統一建模,具體的代碼以下:
private HashMap<UmlClassOrInterface, HashMap<UmlClassOrInterface, Integer>> extensionGraph = new HashMap<>(); //包含繼承關係和接口實現關係 public static <T> void floyd(HashMap<T, HashMap<T, Integer>> graph) { for (T k : graph.keySet()) { for (T i : graph.keySet()) { if (i.equals(k) || !graph.get(i).containsKey(k)) { continue; } int ik = graph.get(i).get(k); for (T j : graph.keySet()) { if (k.equals(j) || !graph.get(k).containsKey(j)) { continue; } int kj = graph.get(k).get(j); if (!graph.get(i).containsKey(j)) { // 更新路徑數 graph.get(i).put(j, ik * kj); } else { int newCnt = graph.get(i).get(j) + ik * kj; graph.get(i).put(j, newCnt); } } } } } protected Set<UmlClassOrInterface> check008() { Set<UmlClassOrInterface> set = new HashSet<>(); for (UmlClassOrInterface i : extensionGraph.keySet()) { if (extensionGraph.get(i).containsKey(i)) { set.add(i); } } return set; } protected Set<UmlClassOrInterface> check009() { Set<UmlClassOrInterface> set = new HashSet<>(); for (UmlClassOrInterface i : extensionGraph.keySet()) { for (UmlClassOrInterface j : extensionGraph.get(i).keySet()) { if (extensionGraph.get(i).get(j) > 1) { set.add(i); } } } return set; }
架構設計是面向對象這門課的重中之重。因爲咱們的課程是按照單元推薦,每一個單元的每一次做業都是在前一次的基礎上,進行增量任務,因此一個好的架構可讓後續的做業更加容易完成。
第一單元:多項式求導
前兩次的做業由於需求還比較簡單,還算是有一個比較看得過去的架構。把多項式分解爲單項式,再把單項式分解成冪函數和三角函數,一個多項式的求導問題就被分而治之。就是下降耦合,抽象出各個不一樣的類,將它們獨立出來,再把每一個對象組合在一塊兒來統一處理。
可是隨着第三次做業的發佈,這個架構設計的弊端就顯現出來了。擴展性較差,致使第三次做業加上嵌套求導以後,幾乎推倒重寫。仍是說明前兩次做業對面向對象機制的理解不夠深入,以及對Java語言掌握不夠熟練(不會使用接口泛型等特性)
第二單元:多線程電梯
多線程電梯調度主要就是要理解清楚生產者-消費者模型以及發佈-訂閱模型這些課上講到的重要的多線程編程的設計模型。多線程最重要的問題就是線程的同步與互斥、線程間通訊的問題。
第二單元多線程電梯調度主要運用的就是「生產者-消費者模型」,構建一個兩級的關係,經過共享隊列在線程間傳遞信息。輸入線程與調度器線程經過共享隊列交互,調度器線程再與電梯線程經過共享隊列交互。這個架構設計延續了三次做業,主要的變化都是調度器內部和電梯內部的調度算法。
這個單元,課上老師也介紹了一些設計模式,好比單例模式、工廠模式等等。雖然實際寫代碼的時候沒有特別注意使用這些設計模式,可是這些學習設計模式的思想,也是在知道咱們怎麼去設計類,怎麼去設計接口,使得程序具備良好的擴展性和魯棒性。固然,最重要的仍是理解了多線程編程的方式和要點。
第三單元:JML規格設計
Java Modeling Language 是用於Java語言建模的語言,是一種契約化的設計思想。這個單元主要是圍繞着圖論展開的,因爲有JML規格做爲提示,總體比較順暢。做業難度依次遞增,從路徑到地鐵圖層層遞進。將不一樣的需求(最短路徑,最少票價,最少換乘)分別用不一樣類的建模完成,下降耦合。因爲都是圖,因此把通用的圖的算法單獨提出來放到一個類,做爲靜態方法來調用(其實這樣是有點危險的,一旦這裏出錯,全部地方都出錯了,嗚嗚嗚我就錯在這了)
JML爲程序的開發設計提供了一個統一的規範規格,雖然估計JML實際應用不是不少,JML描述的規格,對方法、類等程序單元進行了嚴格的約束,這些正確詳實的規格,相較於天然語言,能更加規範地描述需求,減小歧義,保證開發的速度與質量。這種」先設計規格再實現「的約定對於大型工程的協同開發有不少的好處。
第四單元:UML圖解析器
這一單元主要的任務是解析UML圖中的各類元素。理清楚UML各個元素的各個字段的意義以及之間的關係之後,推動的就比較順利。
根據UML圖的組織形式,能夠很容易地聯想到仿照UML圖地組織形式來構建每一個類,按照類圖、順序圖、狀態圖分別構建模型和存儲相應數據。把實現的各部分劃分紅不一樣的責任單元,創建各個類來分別負責完成各自的任務。與第三單元有些相似。
第一單元:多項式求導
學習了討論區編寫測試腳本的方法,我採用以下的方式來發現別人的Bug:
testData.txt
文件中Git bash
執行sympy
用來計算標準答案,以及互測屋全部輸出的答案log.txt
文件中,人工比對使用到的工具包括:Python Sympy, Git bash, VS code等。
第一單元因爲沒有官方包的介入,輸入輸出都是本身處理,因此WRONG FORMAT是測試的重中之重。
第二單元:多線程電梯
多線程編程比較特殊,因爲多線程並行具備不肯定性,且不一樣的調度算法會形成不一樣的輸出結果,因此不存在惟一的正確答案,評測機實現起來也有點複雜。因此我自測階段,沒有進行大量數據的測試。
互測階段,主要是讀代碼,看有沒有出現一下幾種問題:
第三單元:JML規格設計
第三單元使用了很重要的一個測試工具:JUnit。
單元測試是一個強有力的測試工具。相比大量數據的黑盒測試,JUnit單元測試能夠在更快速的找到代碼漏洞,花費更少的時間,達到很好的驗證正確性的效果。JUnit還有一個有點是有測試覆蓋率的指標,這是隨機數據的測試所不能比擬的。
除了使用JUnit進行測試,還構造了一個比較強的隨機數據生成器來進行測試,與同窗的輸出進行比較。(仍是晚了,發現Bug的時候已經截至了)
第四單元:UML圖解析器
第四單元用StarUML花了幾個比較特殊的圖,好比重複繼承、循環繼承相關,以及帶多的環的狀態圖等特殊狀況,來測試代碼的正確性,並重點測試了幾個用到了深度優先搜索算法的指令。期末考試也比較忙,測試頻次不是不少。
測試先行是我印象最深的。
不管是什麼工程,寫代碼、連電路、焊板子這些,最重要的都是進行全面的測試。測試必定要和編寫代碼同步進行,或者先於寫代碼完成。第三單元最後一次做業慘痛的教訓讓我記住了「測試先行」這個道理,不要等到最後再匆匆忙忙測試而後提交,一個隱蔽的錯誤形成的可能就是滿盤皆輸。
一個學期的OO課程終於結束啦。就像登山同樣,如今到了山頂終於能夠喘口氣了。每週的做業走在催逼着本身不斷前進,雖然一個學期基本沒過過一個舒服完整的週末,但回過頭來看,這一萬多行代碼,每一個單元博客的總結記錄,看獲得這一路上的進步和收穫。雖然有作的不盡人意的地方,但仍是蠻有成就感的。
至少最後表彰總結課上沒有空手回去😁
開個測試分享區,能夠分享測試機或者測試數據
第二個是但願互測能有些改變,這個我在第一單元的博客中也寫到過。
這三次的互測後,我相對如今的互測制度提一點小小的建議:
- 仍然劃分A, B, C三檔,可是分組把這三組混合編組,例如8人間能夠{2A, 3B, 3C},7人間{2A, 3B, 2C}
- 找到某個等級做業的Bug得對應等級得分,而與本身的做業等級無關
- 這樣作得好處是:每一個人均可以看到不一樣水平的代碼,給C組和B組的同窗更多學習的機會;避免了高段位「大眼瞪小眼」的尷尬,還有低段位「菜雞互啄」的無趣;找到高段位的Bug更有成就感和分數獎勵
- 缺點是:規則較爲複雜,實現起來可能比較麻煩,並且不必定每一個人都接受這種制度
有同窗說:「通過這三次互測,個人bash腳本和Python 寫的比原來好多了。」
固然,這些都只是我我的的想法,拋磚引玉,還請助教組學長學姐和老師們能研究出更好的制度,迴歸互測的本質。
或者簡單一些,把互測的人數減小到5至6人可能會合適些,這樣能夠多讀代碼,以避免一些同窗互測階段直接放棄。
研討課參與程度不過高,感受「研討」的氛圍不是很明顯。
感受第三單元能夠提到第一單元來。特別是多項式求導做爲第一單元,並且最後一次做業,確實有點困難,不如先從JML開始,逐步熟悉Java語言和麪向對象的建模方式。