面向对象第四单元小结
面向對象第四單元:UML類圖、順序圖、狀態圖
一、架構設計
第一次作業
??
??第四單元第一次作業,需要完成的任務為實現一個UML類圖解析器UmlInteraction,學習目標為UML入門級的理解、UML類圖的構成要素及其解析方法。最終需要實現一個UML類圖解析器,可以通過輸入各種指令來進行類圖有關信息的查詢。具體來說,需要新建一個類MyUmlInteraction,并實現相應的接口方法,每個方法的代碼實現均需要嚴格滿足給出的JML規格定義。
??首先需要搞明白Uml類圖中各元素的含義。我研究出各元素的屬性及含義如下:
??既然是對UML類圖的查詢,最好的架構方式就是仿照Uml類圖的結構,在MyUmlInteraction實現一個類似UML類圖的圖結構。在我的實現中,將每種UmlElement單獨存儲。對于每一種UmlElement,都建立一個從其id到element自身的HashMap映射。下圖中所有前綴為"invert"的屬性均為從其id到element自身的HashMap映射。MyUmlInteraction中所有私有屬性如下圖所示:
??通過以上屬性,便基本建立了一個類似UML類圖的結構。具體映射方式見上面圖中每個屬性的注釋,映射關系顯而易見。
??
??根據generalizationUml屬性存儲了所有繼承關系,可以從子類向上找到所有父類,從子接口找到所有父接口。classCounter屬性建立了類名到次數的映射,記錄了每個類名出現多少次。classUml記錄了從類名到類ID的映射,incomplete字眼意為此屬性記錄不完全,每個類名只記錄了一個對應的ID,重名的類不會記錄下多個ID。innvertClassUml為從類ID到類的映射。AttriCounter屬性記錄了一個類中某個名字的屬性出現的次數。AttriUml記錄了某個類中從屬性名到屬性ID的映射,同樣,此屬性也包含incomplete字眼。invertAttriUml為從屬性ID到屬性的映射。invertOperUml屬性記錄了某個類中從方法ID到方法的映射。paraUml記錄了從方法到其所有參數的關聯關系。invertParaUml為從參數ID到參數的映射。invertAssoUml是從關聯關系ID到關聯關系的反向映射。invertAssoEndUml是從關聯的端的ID到關聯的端的映射。classAsso記錄了類關聯的所有其他類,以便后續指令CLASS_ASSO_CLASS_LIST來查詢。同理,invertFaceUml屬性為從接口ID到接口的映射。realization是從類到其實現的所有接口的映射,以便后續指令CLASS_IMPLEMENT_INTERFACE_LIST進行查詢。
??
??除此之外,還有一些屬性的注釋中標有middle字眼。含有此字眼的屬性是“中間屬性”,將各種查詢指令所運行所查出中間結果進行存儲,以便減小CPU的運行時間。
??
第二次作業
??
??第四單元第二次作業在第一次作業基礎上,擴展解析器,使得能夠支持對UML順序圖和UML狀態圖的解析,并能夠支持幾個基本規則的驗證。一方面需要擴展類圖解析器,使得可以支持對UML狀態圖和順序圖的解析,并可以通過輸入相應的指令來進行相關查詢。另一方面需要在所解析的UML模型基礎上,按照規定的規則檢查模型中是否存在違背規則的情況,并輸出相應信息。
??
??首先依然是對新增順序圖和狀態圖的各種UmlElement的各種屬性的理解:
??
??此次作業的深度不大,但廣度很寬。大部分時間花在了對類圖、順序圖及狀態圖的理解上。順序圖和狀態圖分別有三個函數需要填寫,在理解了各種元素的各種屬性后,寫起來并不費很大勁。并且在討論區助教和老師對測試進行了限制,減少了我們需要考慮的因素。在此基礎上,六個函數便可用很簡潔的思路完成:
??
??對于STATE_COUNT statemachine_name指令,統計state、pseudostate和finalstate元素的個數,其中這些元素的parent(Region)的parent需要為給定的state machine。
??對于TRANSITION_COUNT statemachine_name指令,統計transition元素的數量,其中transition的parent的parent需要為給定的state machine
??對于SUBSEQUENT_STATE_COUNT statemachine_name statename指令,統計從一個狀態可以到達的狀態的數量。
??對于PTCP_OBJ_COUNT umlinteraction_name指令,統計lifeline元素的個數,其中lifeline的parent需要保證為給定的interaction。
??對于MESSAGE_COUNT umlinteraction_name指令,統計message元素的個數,其中message的parent需要保證為給定的interaction。
??對于INCOMING_MSG_COUNT umlinteraction_name lifeline_name指令,統計message元素的個數,其中message的parent需要保證為給定的interaction,且message的target需要為給定的lifeline。
??
??我的程序中,分別創建了MyUmlCollaborationInteraction類和MyUmlStateChartInteraction類來分別處理Uml順序圖和狀態圖。
??
??MyUmlCollaborationInteraction類的屬性如下:
??MyUmlStateChartInteraction類的屬性如下:
??
??兩個圖中的屬性的意義在圖中的注釋中均已標明。同上次作業一樣,屬性的注釋中標有middle字眼的屬性是“中間屬性”,將各種查詢指令所運行所查出中間結果進行存儲,以便減小CPU的運行時間。在構造函數中,將這些屬性存好,后續的函數調用這些屬性進行查詢。值得注意的是,雖然這些屬性看似零散,但其實他們暗含著順序圖和狀態圖的結構。換言之,其實這些屬性存儲了一個完成的圖結構,而后面六個函數則是對圖的查詢和圖的算法的實現。
??
二、程序bug分析
??
??雖然本次博客作業沒有對這一部分進行硬性要求,但我想從一而終,用四個單元的總結博客看到自己的問題,也看到自己的進步。
錯誤一:
類圖如下:
查詢指令:
CLASS_IMPLEMENT_INTERFACE_LIST Class1 CLASS_IMPLEMENT_INTERFACE_LIST Class2 CLASS_IMPLEMENT_INTERFACE_LIST Class3 CLASS_IMPLEMENT_INTERFACE_LIST Class4錯誤輸出:
Ok, implement interfaces of class "Class1" are (Interface1, Interface2). Ok, implement interfaces of class "Class2" are (Interface1). Ok, implement interfaces of class "Class3" are (Interface1, Interface3, Interface4). Ok, implement interfaces of class "Class4" are (Interface1, Interface3).正確輸出:
Ok, implement interfaces of class "Class1" are (Interface1, Interface2). Ok, implement interfaces of class "Class2" are (Interface1). Ok, implement interfaces of class "Class3" are (Interface1, Interface2, Interface3, Interface4). Ok, implement interfaces of class "Class4" are (Interface1, Interface3).錯誤分析:
??
??錯誤出現在第一次作業類圖的相關查詢。錯誤原因是對于繼承關系的認識不足。腦子里一直想著一個類UmlClass最多可以繼承一個UmlClass,卻忽略了一個接口UmlInterface可以繼承多個UmlInterface。所以,在原始的錯誤代碼中,我采用了private HashMap<String, String> generalizationUml;這樣一個屬性來記錄繼承關系,意在反映一個子類到父類(或子接口到父接口)的映射。而對于上面這個案例,Interface4繼承了Interface2和Interface3這兩個接口,那將這兩個繼承關系插入generalizationUml屬性時,就會出現后面一個繼承關系覆蓋了前面一個繼承關系的情況。在此例中,Interface4繼承Interface3這個關系覆蓋了Interface4繼承Interface2這個關系。換言之,generalizationUml屬性根本沒有記錄下來Interface4繼承Interface2這個繼承關系。所以,在查詢Class3實現了那些接口時,會漏掉Interface2這個接口。于是,可以將記錄所有繼承關系的私有屬性generalizationUml更改為:private HashMap<String, ArrayList<String>> generalizationUml;,同樣反映一個子類到父類(或子接口到父接口)的映射,而一個子接口(后子類)可以有多個父接口(或父類)。對于私有屬性generalizationUml的查詢算法同樣需要進行修正。例如,對于CLASS_IMPLEMENT_INTERFACE_LIST這樣的指令,查詢一個類實現了哪些接口,因為一個子接口可以繼承多個父接口,這就需要用一個類似圖的廣度優先搜索的方法進行查詢,而不能僅僅用“不斷找父類,直到沒有父類為止”這樣簡單的做法。
??
錯誤二:
類圖如下:
查詢指令:
CLASS_ASSO_COUNT Class4 CLASS_ASSO_CLASS_LIST Class4錯誤輸出:
Ok, association count of class "Class4" is 4. Ok, associated classes of class "Class4" are (Class4, Class5).正確輸出:
Ok, association count of class "Class4" is 5. Ok, associated classes of class "Class4" are (Class4, Class5).錯誤分析:
??
??錯誤出現在第一次作業類圖的相關查詢。錯誤原因是指令理解有誤。正確的思路是:Class4自身有三個關聯,關聯對端分別是Class4、Class4、Class5;Class4的父類Class3自身有一個關聯,關聯的對端是Class5;Class3的父類Class1有一個關聯,關聯的對端是Interface1;Class1沒有父類。所以第一條指令要求數一數Class4所有關聯的數量,答案就應該是3 + 1 + 1 = 5個。那么我的錯誤代碼漏數了哪一個呢?原本以為,是漏數了Class4的自關聯,實則不然。因為自關聯關系要統計兩遍,而錯誤程序可能只統計了一遍。這個問題在討論區有所強調,所以我格外注意,錯誤并不產生在這里。實際上,我是漏掉了Class1與Interface1的關聯。在處理關聯查詢這一塊兒時,腦子里總想著CLASS_ASSO_CLASS_LIST指令要求只統計關聯對端是類的情況,不統計關聯對端是接口的情況,而忽略了另一條關于類關聯查詢的指令CLASS_ASSO_COUNT既要統計關聯對端是類的情況,也要統計關聯對端是接口的情況。
??
錯誤三:
類圖如下:
錯誤輸出:
(無輸出)正確輸出:
Failed when check R002, class/interface (W11, W112, W14) have circular inheritance.錯誤分析:
??
??錯誤出現在第二次作業規則R002的檢查。R002為不能有循環繼承。錯誤原因是檢查規則R002的函數陷入了死循環,沒有跳出來。具體來說,錯誤代碼中判斷某個類C是否循環繼承的算法是:采用圖的廣度優先搜索BFS算法,若算法成功結束,則表明以C為起點的圖中沒有形成以C為起點和終點的環;否則,若BFS搜索時再次搜索到類C,表明產生了循環繼承。值得注意的是,BFS算法的循環跳出條件是:循環隊列為空或再次搜索到起點。事實上,若按上述算法,對于該案例,算法可以發現類W11,W12,W14都有循環繼承,而對于類W15,以W15為起點進行BFS搜索,W11,W12,W14這三個類會不斷被加入隊列,導致死循環。對于此錯誤的改進方法是:用一個Set記錄所有進到過隊列中的元素,BFS算法每次遍歷到的結點需要先經過一層判斷,若Set中包含該結點,則表明此結點形成了環,已經被遍歷過,不能加入隊列中。
??
三、四個單元的架構設計及OO方法理解演進
3.1 第一單元架構設計
??
??第一單元為Java語言及面向對象機制入門,三次作業難度由淺入深,從類圖便可看出。第一次作業簡單多項式的求導,對于Java不熟練,仿照C語言的方式寫一兩個函數便可完成;而第三次作業暗含嵌套結構以及繼承的特征,若繼續用以前C語言的編碼方式,代碼量將成倍地上漲。只有活學活用Java的面向對象機制,掌握繼承與接口封裝,才能事半功倍。第三次作業使用了繼承的Class定義方式,而前兩次作業都未使用。例如,Const、Power、Sin、Cos、(Expr),這幾類都屬于因子,因此我們可以定義一個Factor類作為父類,上述五個類作為Factor的子類拓展其屬性和方法。若按此繼承方法封裝類,在求導、合并同類項時,Java的多態機制便可以體現得淋漓盡致,減少了編碼量,同時更使得代碼看起來整齊易懂。
??
3.2 第二單元架構設計
??
??第二單元引入了線程的概念,用多線程機制來模擬實際生活中的多電梯調度系統。本單元可分為三次難度不斷遞進的小作業:單步多線程傻瓜調度(FAFS)電梯的模擬、單步多線程可捎帶調度(ALS)電梯的模擬、多部多線程智能調度(SS)電梯的模擬。
??
??第一次作業我構建了一個隊列Queue用來管理請求PersonRequest,它相當于生產者消費者問題中的共享數據區。電梯線程仿佛消費者,不斷從Queue里取出請求并滿足該請求;任務接收器仿佛生產者,只要ElevatorInput出現了新的請求,就將其加入隊列。當共享數據區為空時,需要阻塞電梯線程繼續消費數據;這對應于實際生活中若沒有客人請求使用電梯,電梯就應該暫停運行。而與經典生產者消費者問題不同的是,Queue沒有長度限制,也就是不存在隊列已滿無法接收請求的狀況。由于任意調度策略均視為正確,我們采用最樸素的先來先服務調度(FIFO)原則,這也與共享數據區是一個隊列Queue結構相符。電梯線程每次從Queue中取出一個請求,電梯內最多只有一個乘客,待滿足完該乘客的請求后,再從Queue中取下一個請求進行滿足。所以電梯類需要保存的屬性是共享隊列Queue,和電梯當前到達的樓層floor兩個屬性。數據共享產生沖突,致使線程不安全。在我的設計中,電梯線程和任務接收器線程(也就是消費者線程和生產者線程)不做任何wait(), notify(), lock, syncronized的處理。只要保證共享數據區Queue的安全性,生產者和消費者線程就不會數據沖突問題。電梯線程從Queue取請求,任務接收器向Queue中加入請求。可能產生的沖突在于Queue的讀寫沖突和寫寫沖突。只要將對于Queue的讀寫操作用鎖機制保護起來,就保證了程序線程安全。
??
??第二單元第二次作業換湯不換藥,除了調度策略需要有所改動外,生產者消費者機制依然可以套用。程序依然分為三個線程:主線程、電梯線程、任務接收器。構建一個隊列Queue用來管理請求PersonRequest,作為共享數據區;電梯線程作為消費者;任務接收器作為生產者。與第一次作業不同的是電梯調度策略的改進,電梯內不能僅僅是一個人,運行捎帶。于是對于電梯線程,每當其停靠一個樓層時,都要判斷在Queue中是否有請求可以捎帶。捎帶請求的條件是:該請求的初始樓層與電梯當前停靠樓層一致;且該請求要行走的方向與當前電梯運行方向一致。所以,對于電梯的屬性,需要在第一次的基礎上有兩點改進:加入direction屬性記錄電梯運行方向,以便對應捎帶請求的判斷條件;再加入一個列表記錄當前電梯內的人數和當前電梯內的乘客所對應的請求信息。線程安全問題依然出在共享資源的讀寫沖突和寫寫沖突中,與前面一次作業相同,只要用鎖機制保證對于共享隊列Queue的操作絕對安全即可。
??
??第二單元第三次作業采用5個線程加三個共享隊列的方式實現。三個線程分別負責模擬三部電梯的運行,一個線程作為任務接收器,另一個是主線程。三個共享隊列分別存儲著A、B、C三部電梯需要滿足的請求:隊列a存儲著電梯A需要滿足的請求,隊列b存儲著電梯B需要滿足的請求,隊列c存儲著電梯C需要滿足的請求。任務接收器線程除了接收請求外,還擔負著初步分配請求的任務。每當ElevatorInput讀到一個請求,接收器根據其起始樓層判斷該乘客需要首先乘坐哪個電梯,并生成一個“中間樓層”。若某電梯可以直達乘客的目的樓層,則中間樓層等于目的樓層;否則中間樓層與目的樓層不等,表示乘客需求無法一次性滿足,該乘客需要換乘其它電梯。接收器的任務是將請求拆成起始樓層,到中間樓層,再到目的樓層。將中間樓層的數值和乘客所有信息根據乘客初始需要乘坐的電梯一并存入隊列a、b或c中。同時,對于需要換乘的乘客(中間樓層與目的樓層不等),生成一個新的請求,將此請求放在“等待緩沖池”里。“等待緩沖池”的意義是,請求未被激活,當乘客從起始樓層到達中間樓層后,表示乘客可以換乘,此時,等待緩沖池中的隊列才被激活,處于可執行狀態。這樣,我們就把第三次作業成功拆分為了三個第二次作業的組合。每個電梯線程分別有各自的請求隊列,按照第二次作業中的ALS可捎帶調度策略按方抓藥,運行各自的線程。但值得注意的是,電梯線程仍有一點需要改變。即乘客需要離開的電梯的樓層是其中間樓層,而非目的樓層。所以,需要加入一點改進:當乘客到達中間樓層離開電梯后,判斷乘客的是否已達最終目的地(中間樓層是否等于目的樓層),如已達最終目的地,則乘客請求已滿足;否則,需要換乘,從等待緩沖區中找出對應該乘客從中間樓層到目的樓層的請求,激活喚醒該請求,加入對應共享隊列。所以,在電梯類的屬性中,還需要在第二次作業電梯類的基礎上加入等待緩沖池,以便乘客到達中間樓層后訪問等待緩沖池。
3.3 第三單元架構設計
??
??第三單元規格化的面向對象設計方法。第一次作業,需要完成的任務為實現兩個容器類Path和PathContainer。最終實現的是一個路徑管理系統。可以通過各類輸入指令來進行數據的增刪查改等交互。第二次作業,需要完成實現容器類Path和數據結構類Graph,最終需要實現一個無向圖系統。可以像PathContainer一樣,通過各類輸入指令來進行基于路徑的增刪查改管理。還可以將內部的Path構建為無向圖結構,進行基于無向圖的一些查詢操作。第三次作業需要完成實現容器類Path和地鐵系統類RailwaySystem,最終需要實現一個簡單地鐵系統。可以像PathContainer一樣,通過各類輸入指令來進行基于路徑的增刪查改管理;還可以將內部的Path構建為無向圖結構,進行基于無向圖的一些查詢操作;再可以構建一個簡單的RailwaySystem地鐵系統,進行一些基本的查詢操作。
??
??對于第三單元第一次作業,我設計的MyPath類有兩個屬性:trajectory屬性為一個ArrayList,即對應構造方法中傳入的nodeList;set屬性為一個HashSet,是軌跡中編號不同的點的集合。MyPathContainer類有如下四個屬性。dataset是一系列Path對象組成的ArrayList,用來記錄容器中的所有軌跡。id是一系列pathId組成的ArrayList,index為i的pathId對應dataset中index為i的path。nodeCounter屬性是一個HashMap,用來記錄容器中不同節點出現的個數。edgeCounter屬性也是一個HashMap,用來記錄容器中不同邊出現的個數。nodeCounter屬性和edgeCounter屬性都是為了應對DISTINCET_NODE_COUNT指令所生成的私有屬性。并且,nodeCounter屬性和edgeCounter屬性這兩個屬性基本建立了一張圖,這對于作業二和作業三的拓展也是大有裨益的。值得注意的是,edgeCounter屬性在本次作業的方法中并沒有應用,只是為了可擴展性的架構添加的屬性。在本文下一自然段中可以發現edgeCounter屬性在后續作業中求最短路徑發揮了重要作用。
??
??由于第一次作業可擴展性架構做得很好,第三單元第二次作業只加了get_shortest_distance這一個最主要函數,所以第二次作業做起來較為輕松,第一周在架構和設計上花費的時間在第二周得到了回報。MyPath類沒有變化。MyGraph類在MyPathContainer類的基礎上變化也不大,延用第一次作業的nodeCounter屬性和edgeCounter屬性這兩個屬性基本構建圖的結構。nodeCounter屬性用來記錄容器中不同節點及其出現次數。edgeCounter屬性用來記錄容器中不同邊及其出現次數。圖的構建完畢。針對最短路徑的計算,加入distance屬性。distance屬性是一個HashMap,key值是無向圖中的節點編號,value值是一個從節點編號映射到最短距離的HashMap。distance記錄了從節點到其可達節點的最短距離。最短距離算法:考慮到相鄰節點之前的距離為1,即這是一幅權重全為1的特殊無向圖。于是,BFS廣度優先搜索就可以派上用場,而不必應用Dijkstra算法。從某一節點fromNode出發對圖進行廣度優先搜索,搜索到的節點順序其實與Dijkstra算法搜索的節點順序是一致的,這就保證了算法的正確性。所以以某一節點fromNode出發對圖進行一次BFS搜索算法,可以得到fromNode到圖中所有結點的最短距離。為了節約CPU運行時間,并不是每次addPath或removePath時都進行一遍圖的廣度優先搜索。每次用戶進行 get_shortest_distance 查詢時,先查詢distance屬性中是否有與fromNodeId相同的鍵值key,若有,則表示之前以fromNode為出發點進行過BFS搜索,則直接讀取相應結果,省去了BFS搜索的時間;若沒有,再進行以fromNode為起點的BFS搜索,并加搜索結果加入distance屬性中。換言之,distance屬性并不記錄無向圖中所有起點到其它所有點的最短距離,而只是記錄一部分起點到其它所有點的最短距離。
??
??第三次作業在第二次作業基礎上主要增加了查詢最低票價、最小換乘次數、最小不滿意度的計算。由于是需求是不斷遞進關系,每次的作業其實都可以繼承上一次作業的類。例如,我在此次作業實現MyRailwaySystem類時,就繼承了第二次作業的Graph類,因為除了功能在遞進外,本質上地鐵系統圖就是一個更具體的無向圖。MyPath類沒有變化。而MyRailwaySystem類隨繼承了MyGraph類,但仍然針對最低票價、最小換乘次數、最小不滿意度的查詢功能增加了私有屬性。nodeSet屬性和edgeSet屬性存儲了地鐵圖結構,price、transfer、unpleasant三個HashMap屬性原理與第二次作業中distance屬性類似,分別記錄了從結點到圖中所有其他結點的最低票價、最小換乘和最小不滿意度。與distance屬性類似,為了節約CPU運行時間,price、transfer、unpleasant三個屬性并不記錄無向圖中所有起點到其它所有點的最低票價、最小換乘和最小不滿意度,而只是記錄一部分起點到其它所有點的最低票價、最小換乘和最小不滿意度。最低票價、最小換乘和最小不滿意度算法:最低票價、最小換乘和最小不滿意度算法原理完全相同,都是有向圖的最短路徑問題,只是各自的邊權重不一樣。這里以最小不滿意度算法具體展開。由于邊權重不再全是1,所以不能簡簡單單像第二次作業一樣使用BFS搜索算法。我這次采用了傳統的Dijkstra算法,但又與傳統的Dijkstra算法稍有不同。由于引入了換乘,換乘的不滿意度不為0,傳統的Dijkstra算法不再具有最優子結構,我的解決方法是對地鐵圖做一定的改進,采用拆點的做法。具體來說:對于每個地鐵站node,拆分成2+x個點,其中x為經過這個地鐵站的Path數。其中,前兩個點為抽象出來的起點和終點,用于解決換乘和答案統計,后面的x個點可以理解為每個Path在地鐵站node的站臺。之后是連邊,設一條Path中相鄰兩個點的邊權為x(雙向邊),每個地鐵站的終點到起點(沒有寫錯)連邊的邊權為y(換乘,單向邊)。x,y的值由需求決定(即四種圖)。每個站臺往它所在的終點連邊的邊權為0,每個地鐵站的起點往它的每個站臺連邊的邊權為0,圖即建好。換言之,將圖結構進行了改進后,圖即變成了一個普通的有向圖,傳統的Dijkstra算法在求解最小不滿意度時由于具有了最優子結構,可以發揮作用。
3.4 第四單元架構設計
??
??見本博客第一節。
??
3.5 OO方法理解的演進
??
??一個學期的面向對象課程學習下來,發現自己真的長進了不少。回首這一個學期,發現四個單元的教學設計是不斷遞進的,或者說是“繼承”關系,或至少是強“關聯”關系。
??
??本課程的4個單元模塊分別是Java基礎、多線程和線程安全設計、抽象與規格,測試和論證。
第一單元對象與對象化編程。主要接觸了Java語言的基本語法和面向對象的設計思維,可以設計出較為簡單的單線程程序,是OO課程的基礎鋪墊。
??
??第二單元Java運行機制及多線程。從此單元開始,難度明顯上升,開始設計多線程交互系統,一方面需要實現線程間的并行,一方面需要著重注意線程安全問題。
??
??第三單元規格化的面向對象設計方法。主要進行對程序抽象與規格的設計,便于養成良好的工程化開發的習慣,同時借助對前面作業的規格設計,來進一步深入理解面向對象的編程思維。
??
??第四單元UML語言。主要則是引入了另一種語言,直接提供針對性、分離的結構與行為描述手段,而且可以在后臺把描述元素整合起來。從UML類圖到順序圖再到狀態圖,讓我看到了Java程序如何與UML結合,反映自己程序的優缺點。
??
??除了四個單元之間的遞進式架構外,每個單元的三次小作業也具有遞進關系。從3.1 ~ 3.4小節的作業架構便可看出。比如第三單元的第一次作業中edgeCounter屬性就很好地為第二次作業中最短路徑的求解鋪平了道路,因而我們可以認為第三單元第一次作業的架構是好的架構,是具有可擴展性的架構。
??
??除了遞進式的架構外,四個單元的學習還讓我學會如何工程化地開發軟件。工程化開發的一般流程是需求分析—設計編碼—測試驗收—維護。“需求”是基本問題,既是出發點也是終結點,基于需求進行設計編碼,測試驗收,最終實現需求。但有時候需求也不是一成不變的,就比如我們的OO作業都有可能寫著寫著改指導書了,readme(對于程序員來說)是最好的解決辦法,但在具體現實問題中可能事與愿違,如果前期設計包容度不夠高,可能一個微小需求的變動會對整個工程開發產生很大的影響,所以,前期設計至關重要,個人認為,可能需要考慮的比需求本身更為廣泛,多想一些,想長遠一點。除此,一般而言,對于大型的工程項目,多是teamwork,這就需要程序員具有良好的編程習慣以及職業素養,并且在團隊協作中,可能需要事先設置一些規范要求以便最終能實現高效的任務對接,但如果某個個體違背了一定的原則,且不說對整個工程開發的影響,這種行為本身就該被譴責。
??
??OO課程為我們打開了以后步入程序員崗位的一扇小窗,讓我們在一輪又一輪作業中強化體會這四個環節。特別是設計和實現分離,設計好了,實現也就是解決一些小細節而已,要是沒設計好就急忙動手,那必然是步步兇險且是蝸牛式前進。
??
四、四個單元的測試理解及實踐演進
??
??值得說明的是,16級高工沒有OO互測,沒有機會去學習其他同學的代碼。但發現別人程序Bug的策略和發現我自己程序Bug的策略其實是基本一致的。本博客中僅根據四個單元中在弱中強測中發現的Bug,以及自我的其它程序測試進行分析。
??
??在碼好代碼后,首先要根據自己的代碼結構思考可能出現的問題,再加之各種覆蓋性測試和魯棒性測試。即先進行白盒測試,再進行黑盒測試。白盒測試是通過程序的源代碼進行測試而不使用用戶界面。這種類型的測試需要從代碼句法發現內部代碼在算法,溢出,路徑,條件等等中的缺點或者錯誤,進而加以修正。黑盒測試又被稱為功能測試、數據驅動測試或基于規格說明的測試,是通過使用整個軟件或某種軟件功能來嚴格地測試, 而并沒有通過檢查程序的源代碼或者很清楚地了解該軟件的源代碼程序具體是怎樣設計的。測試人員通過輸入他們的數據然后看輸出的結果從而了解軟件怎樣工作。
??
??每次作業測試著重測試作業的創新點。例如第一單元的第三次作業在前兩次作業的基礎上又允許三角函數中嵌套因子的新規則,就著重設計對嵌套因子的求導的測試。再如第一單元第三次作業重構了代碼,五種因子類都是Factor的子類,覆蓋測試時就要覆蓋全面,各種因子都嵌套一遍,都相加減一遍,都相乘一遍。我自己書寫了兩個程序:①隨機生成項和因子的代碼,用于生成覆蓋性測試的測試集;②提取自己程序的求導結果,代入MATLAB中驗證正確性的比對代碼。
??
??再如第二單元的作業,我同樣編寫了Python程序已完成覆蓋性測試。隨機生成一系列的請求,PersonId依次遞增,fromFloor和toFloor為在樓層范圍內的隨機整數(fromFloor不等于toFloor)。模型產生大量隨機請求,驗證程序的正確性和效率是否在datacheck給出的時間范圍之內。
??
??魯棒性測試一樣重要。每次作業的需求都有不小的改變,第一單元中字符串的輸入處理會千變萬化,很有可能考慮不周;第二單元單次運行程序很可能不會有錯,但多次運行程序可能才會發現線程安全問題;此外,沒有任何請求的系統、只有單一請求的系統、只用到一部電梯的請求、錯誤請求、很多人同時從一層上電梯的請求......等等特殊情況都要覆蓋全面。所以第二單元我著重設計了魯棒性測試。與第一單元進行黑盒測試和白盒測試相比,第二單元更側重線程安全的角度,所以更注重魯棒性測試,看看程序是否會出現死鎖或無限等待的情況。第三單元對于給定的規格,同樣需要將規格的每個分支測試都覆蓋全面。
??
??除了上述測試之外,我還學會了如何使用JUnit進行測試。通過JMLUnitNG的安裝,可以自動生成相應的測試代碼對代碼段進行測試。從測試樣例可見,使用JUnit進行測試,測試強調了方法的魯棒性。例如對整數范圍內的最大值、最小值、0、空值及其組合都會進行相應測試。所以,在第四單元和今后的Java程序中,除了自主編寫測試程序和測試用例外,我還學會了結合JMLUnitNG進行自動的程序測試。
??
五、課程收獲
??
??一個學期的面向對象課程學習下來,發現自己真的長進了不少。縱觀四個單元面向對象編程作業的完成過程,從臃腫的代碼方式到后面較為抽象的設計實現,我的編程思維發生了不小的轉變。
??
??在設計上,在對功能需求進行分析之后,帶著“高內聚、低耦合”的思想,可以抽離出相應的類來均衡實現相應的功能,并使這些類相互交互來實現整個系統的功能。例如,第三單元中的作業是逐級遞進的。這與一般用戶的需求相近,每一次的功能是單調遞增的,而且具備一定的繼承性。這個系列作業,從PathContainer,到Graph再到RailwaySystem,很形象地描述了實際OOP開發中,一個功能模塊的演化過程。從低層次抽象,逐步形成一個面向實際需求的模塊。這對我今后步入工作崗位或科學研究都是很有幫助的。另外,每次做作業前應注意架構和設計,所有在設計上花費的時間都會在未來收到回報。不必著急開始動手編寫代碼,要事先考慮好圖的結構用什么存儲,最短路徑用什么算法實現等等,此外還要考慮好結構的可擴展性。
??
??在測試上,除了會應用Python編寫黑盒測試用例,還能夠不斷針對特殊點完善自己程序的魯棒性。最后還學到了擁有JML規格后基于junit的自動化測試,來對程序進行邏輯驗證。
??
??在程序質量上,除了魯棒性的考慮更加完善外,程序容錯率也有所提升。之前寫代碼不會考慮一些不可能的因素,現在編寫會盡量將if-else或switch的每個分支寫全面,再如對HashMap的查詢盡量使用getOrDefault方法,而不去使用get方法。盡管在設計時會認為有些分支條件不可能被觸發,但一個完整的、規格化的設計方式不但可以避免很多bug的出現,還能讓自己和其它程序維護的人看起來清楚明了。
??
六、改進建議
??
??1、首先是大的教學方法上,面向對象課程和計算機組成課程的授課方法是有很大區別的。計算機組成課程的教師會對課下的編程作業做很多架構上的分析,老師帶領所有同學一起思考如何架構會是最佳方式;而面向對象課程的老師則是課上帶著大家一起討論如何架構的時間有限,更多是讓同學課下思考與討論最合理、可延續性的架構方式。因為如果不能自己做出架構,就不會有真正的技術掌控力。這樣的做法雖然讓我走了不少彎路,比如某次作業由于架構不合理,需要在上一次作業的基礎上重構代碼;但也在一個學期下來培養了我高內聚、低耦合,面向對象的思維方法。可能是因為計算機組成是我們進入計算機專業后第一座大山,同學經驗有限,需要老師更多帶領,而OO課程的老師想培養我們自己探索、自己思考的能力。我無法確定地說,這兩種教學方式哪一種更好,但是這個問題我想在這里提出來,供老師和各位同學思考討論。
??
??2、其次,可能也是課程組考慮到了上一自然段所述的問題,同學們可能在自己思考探索后還是沒有做到最佳的架構方式。在每個單元結束后,助教都會整理出幾份推薦代碼供同學們學習。由于這部分內容并非強制學習,且推薦的內容是上一個單元的作業,往往與當下手頭的作業沒有直接的關系,導致大部分同學無暇去看,或懶得去看。建議是這部分內容變成強制性學習內容,比如可以納入到總結性博客作業中。
??
??3、此外,是課程指導書的下發。這一年OO課程的教學內容較前一年改變很大,所以有時指導書下發推遲或內容不完善也可以理解。但是我們希望能麻煩OO課程組對這一學期下來同學們提出的指導書的Bug和解釋不充分的地方進行補充完善,能為下一屆學習OO的同學提供更多便利。
??
??以上建議雖然語言上可能看似比較犀利,但是發自肺腑。我在一個學期的學習下來確實提升了自我,所以也由衷地希望這么課能越來越棒!
??
??總的來說,我認為從去年的課程內容再到今年,面向對象課程組的教學內容和方式在不斷地完善和升華。也特別感謝助教們對于我們編程作業的細心解答。更要感謝吳際和榮文戈兩位老師的教學,課程清楚明白,切中重點,培養了我面向對象的設計思維和設計方法。祝愿OO課程組越來越好。
??
轉載于:https://www.cnblogs.com/16231181guorongchen/p/10981802.html
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的面向对象第四单元小结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端开发流程与组件化
- 下一篇: GridSearchCV和Randomi