BUAA_OO_U4_Summary 一 / 架構設計 1.0> 題目解析 實現UML類圖的分析。 1.1> HW13 1.1.1> 關於UML 從JML到UML,對於從模型到實現的能力訓練,此單元從上一單元的Java Modeling Language升級到了Unified Modeling La ...
BUAA_OO_U4_Summary
目錄一 / 架構設計
1.0> 題目解析
實現UML類圖的分析。
1.1> HW13
1.1.1> 關於UML
從JML到UML,對於從模型到實現的能力訓練,此單元從上一單元的Java Modeling Language升級到了Unified Modeling Language——統一建模語言。它是通用的,不是針對某一種語言而制,
截止UML2.0,一共有13種圖模型。在pre中便已讓我們接觸了類圖,在接下來兩個單元的總結中本人也對著代碼繪製了相應類圖(但其實老師強調了多次,這是一個錯誤的順序,應該先有類圖,再對照著類圖形成代碼,所以這個錯誤的順序只能更加凸顯本人架構能力的脆弱,只能邊寫邊想從而產生一些不必要的重構)。第二個單元在處理線程問題時也用到了順序圖。在這個單元中新增了對狀態圖的認識。
- 類圖:描述系統中類的靜態結構。
- 順序圖:對象之間的動態合作關係,強調對象發送消息的順序,同時顯示對象之間的交互
- 狀態圖:是描述狀態到狀態控制流,常用於動態特性建模
1.1.2> 關於類圖
這裡借用第四單元手冊裡面的一張圖。它已經很清晰的展示了類圖裡面的元素及其結構。本人在編程時也採用的此結構進行的所需類的設計,可見下麵的代碼架構部分。
1.1.3> 找到全部父介面
這個單元說難也不難,說簡單也不簡單(至少令本蒟蒻感到比較頭疼),一個是讀不懂指導書,另一個就是拿不准應該怎麼實現。最後本人的所有功能都是能跑但是暴力的做法,沒有追求任何時間上的優化了(因為實在來不及優化了)。但是在找父介面的地方,如果每次都遞歸到曾曾…曾祖父去,未免太浪費時間了,所以採取記憶化搜索的方式。
按理說,記憶化搜索很好寫,即如果已經搜索過這個介面,那麼這個介面的全部父介面都應該已經被存儲到這個介面里了,直接拿來用即可,否則繼續遞歸下去。但是對於一個java數據結構新手來說,遇到Null Pointer Exception已經是家常便飯了,這種時候有兩種解決方式,一個是單步調試,看是哪個語句報了錯,另一個是利用idea的流調功能,其實和單步調試很相似但是不用自己去運行了。
1.1.4> 代碼構架
MyImplementation:用於實現官方包的介面,但是在這一次作業中,本人把大量的處理代碼都塞在了這個類中,導致行數極多,翻起來也非常複雜
MyClass:通過組合封裝了一個UmlClass
,用於處理類之間的關係(父類、子類、聯繫等)和類自身的屬性(擁有的屬性、方法等)
MyInterface:通過組合封裝了一個UmlInterface
,用於處理介面之間的關係(泛化等)和介面自身的屬性(擁有的方法)
MyOperation:通過組合封裝了一個UmlOperation
MyType:用於判斷一個UmlParameter
的類型是否合法,在這裡本人卡了很久,因為遲遲沒有發現怎麼使用官方包進行類型的判斷,直到發現需要先轉型然後才能用NameType
中的類型進行字元串比較。
1.2> HW14
新增對狀態圖和順序圖的分析。
1.2.1> 關於狀態圖
再次從手冊里截一張圖。
狀態圖相對來說比較好理解,一方面原因是它長得就是一個狀態機(在上個學期已經被折磨過了),另一方面是第四單元手冊中對於狀態圖的講解非常詳細,還有原因就是其中涉及的結構相較來說簡單一些。如上圖右上角所示,狀態機表示的就是從一個狀態到下一個狀態,需要什麼條件誘發,誘發的結果是什麼。
1.2.2> 關於順序圖
順序圖在第二單元便已經接觸過了,但是當時畫的那幅圖一言難盡,首先是混亂,其次是用錯了很多箭頭。通過這次的手冊本人也深刻意識到了這兩點。
圖片依舊來自於第四單元手冊。
這個結構圖看起來就更加清晰明瞭了。但是很有趣的一點是,UmlAttribute不僅在類圖裡作為class的屬性出現,也可以作為狀態圖中表示協同行為的屬性成員,每個UmlLifeline
都關聯到一個UmlAttribute
,也就是對應了一個具體的對象。
1.2.3> 代碼構架
這張圖以MyImplementation類為中心,左上是狀態圖,左下是順序圖,右側是類圖的元素們。
MyImplementation:用於實現官方包的介面,這次作業沒有新建utils類,並把這個類控制在了剛好500行的位置
MyClass:通過組合封裝了一個UmlClass
,用於處理類之間的關係(父類、子類、聯繫等)和類自身的屬性(擁有的屬性、方法等)
MyInterface:通過組合封裝了一個UmlInterface
,用於處理介面之間的關係(泛化等)和介面自身的屬性(擁有的方法)
MyOperation:通過組合封裝了一個UmlOperation
MyType:用於判斷一個UmlParameter
的類型是否合法
MyStateMachine:通過組合封裝了一個UmlStateMachine
,用於保存當前狀態模型最頂層的結構。但是本單元作業保證了每個StateMachine
下麵只有一個Region
,所以這個類可有可無,本人只是想讓自己代碼的類圖看起來完整一點
MyRegion:通過組合封裝了一個UmlRegion
,裡面存儲了所有的state
和所有的transition
,是一個畫布的存在
MyState:通過組合封裝了UmlState
、UmlPseudostate
和UmlFinalState
,並設置變數記錄狀態的類型,存儲從此狀態遷出的trasition
集合
MyCollaboration:通過組合封裝了UmlCollaboration
,存儲所有的attribute
和interaction
MyInteraction:通過組合封裝了UmlInteraction
,存儲所有的消息和生命線
MyLifeLine:通過組合封裝了UmlLifeline
1.3> HW15
1.3.1> 迭代開發
對類圖、狀態圖、順序圖中的異常進行判斷。
1.3.2> 讀不懂指導書
對於這樣一條R002的規則,給出的解釋如下:“針對類圖中的類(UMLClass),其成員屬性(UMLAttribute)和關聯的另一端所連接的 UMLAssociationEnd 這兩者構成的整體中,不能有重名的成員。”
拆詞能看出來,他讓我找成員屬性+關聯+另一端的連接+整體+重名+成員。成員屬性和成員是一個東西嗎?我只知道屬性是屬性,UMLAttribute可以被當做是屬性,現在這個解釋告訴我它也教成員屬性,那成員究竟指什麼?雖然最後的事實證明,屬性=成員=成員屬性。整體又是什麼?自己這個類中的這個屬性和關聯的另一個類或介面的全部屬性,以及兩個UmlAssociationEnd構成了這個整體的入侵時成員,所以只需要再這個裡面找重名的即可。但是理解這個概念遠比我寫完這次作業所需時間長的久——現在看懂了,便有億點體會不到當時那種焦慮無助的感受了,但指導書中的各種文字真的充滿了陌生與掙扎。
1.3.3> 代碼構架
這個是完整版。這張圖以readDiagram類為中心,左上是類圖,左下是順序圖,右側是狀態圖圖的元素們。
MyImplementation:用於實現官方包的介面,新建的Utils類分走了很多代碼
ReadDiagram:工具類,用於讀入
MyClass:通過組合封裝了一個UmlClass
,用於處理類之間的關係(父類、子類、聯繫等)和類自身的屬性(擁有的屬性、方法等)
MyInterface:通過組合封裝了一個UmlInterface
,用於處理介面之間的關係(泛化等)和介面自身的屬性(擁有的方法)
MyOperation:通過組合封裝了一個UmlOperation
MyAssociation:通過組合封裝了一個UmlAssociation
,用於保存關聯關係的兩端
MyElement:慶祝一下蒟蒻終於使用了介面這件事。其實沒有必要,這個只是作為統一MyClass和MyInterface,便於跑dfs和bfs處理異常的時候用
MyType:用於判斷一個UmlParameter
的類型是否合法
MyStateMachine:通過組合封裝了一個UmlStateMachine
,用於保存當前狀態模型最頂層的結構。但是本單元作業保證了每個StateMachine
下麵只有一個Region
,所以這個類可有可無,本人只是想讓自己代碼的類圖看起來完整一點
MyRegion:通過組合封裝了一個UmlRegion
,裡面存儲了所有的state
和所有的transition
,是一個畫布的存在
MyState:通過組合封裝了UmlState
、UmlPseudostate
和UmlFinalState
,並設置變數記錄狀態的類型,存儲從此狀態遷出的trasition
集合
MyTransition:通過組合封裝了UmlTransition
,存儲其上的所有event
MyCollaboration:通過組合封裝了UmlCollaboration
,存儲所有的attribute
和interaction
MyInteraction:通過組合封裝了UmlInteraction
,存儲所有的消息和生命線
MyLifeLine:通過組合封裝了UmlLifeline
稍微更清晰但簡略的版本如上圖所示。本想歸類更明顯一點,奈何這個連線動起來實在是太麻煩。但是大致可以看出來,從左往右分別為類圖、狀態圖和順序圖的架構。
二 / 架構設計思維及OO方法理解的演進
2.1> 架構設計
儘管每個單元各有各的側重點,但是在所有的博客中,必要的環節都是架構分析。它是一個抽象,一個對於我需要實現的任務目標的抽象,不考慮實現細節,不需要考慮每個對象如何工作,只考慮需要什麼對象,他們各自做什麼的過程。就像一個大老闆,安排好任務,怎麼實現是勞動力的事情——這就很面向對象了。Uml圖是很好的一個表達方式,但可惜本人有此意識而無此行動,每次都是寫完代碼才著手繪製UML圖,在其之前都只是在腦子中構想我需要怎麼化解這個任務。
第一單元的目標是解析表達式,所以對象就是表達式。架構設計體現在把這些表達式分類,分成多項式、單項式等的抽象,然後進行處理。
第二單元的目標是讓電梯安全的運行,涉及到的對象有電梯、請求、生產著消費者模型中的托盤等,在套用改模型的前提下需要考慮生產者和消費者都是什麼的問題,其餘更多的線程安全實現不在架構考慮範圍內。
第三單元跟著JML走,通過JML告訴我們的社交網路中基本單位(人、小組)和需要實現的查詢方法構建整個社交網路的結構,是架構非常清晰的一個單元。
第四單元分析UML,架構同樣很清晰,於是直接採用了UML類圖的架構。三種不同的圖示類型也讓本人清晰的瞭解了三種處理不同問題的架構應該是怎樣的,只不過這個架構是從比確定問題中的對象是什麼而言更抽象、更高的角度,來看待一個問題可以被劃分成類、介面或是各個狀態、各個生命線的模樣的方法。
2.2> oo方法理解
第一單元主要聚焦點有點跑偏到實現方面了,也就是遞歸下降。對於面向對象的直觀體驗是從表達式中抽象出一個最小的不考慮它內部如何處理的單位,然後不斷合併為新的可處理的對象,直至合併至整個表達式。
第二單元學習了不少設計模式,這是面向對象編程的基礎。面向對象註重行為,設計模式就告訴你行為的藍圖。而設計模式的五大基本原則:單一職責、開閉(對擴展開放,對修改封閉)、依賴倒置(細節依賴於介面,針對介面編程,而不針對實現編程)、介面隔離(使用多個專門的介面)、里氏替換原則(子類必須能夠替換掉它們的父類)在後續的作業中,讓本人的架構設計和麵向對象思想變得更清晰了一些,特別是幫助本人降低類與類之間的耦合度方面,以及降低複雜度,讓代碼變得易於調試減少錯誤方面都有著很大的提升。
第三單元中,JML的規格約束讓本人更加深入的理解了面向對象的意義,即利用的高度抽象,清晰明確的展現需求,不考慮步驟而關註功能,不註重過程而關註行為的解決問題視角。
第四單元中,UML這樣的面向對象的建模語言,讓本人在實現的同時回顧了整個oo中用到的類、介面等元素以及繼承、泛化、組合、依賴、實現、關聯這六種關係,更加深刻的體會了面向對象的建模方式。
三 / 測試理解與實踐的演進
其實本人只在一三兩個單元做了測試程式的編寫(包括評測和對拍),二四單元沒有寫評測機。從錯誤的數量來看,明顯沒有進行自行評測的兩個單元得分更低,所以如果時間允許,多做測試。
從實踐方面,在第一單元作業中,本人學習瞭如何使用python的subprocess模塊中的popen,第一單元採用通過開啟pipe來和程式交互,第三單元輸入量較大,輸出量也大,所以如果輸入輸出都使用pipe進行的話,管道會炸掉,所以改用輸出至文件的方式。
如第一單元:
process = subprocess.Popen(['java', '-jar', jarDir], stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate(strInput.encode())
ret = []
ret += stdout.decode().split('\n')
process.kill()
第三單元:
process = subprocess.Popen(['java', '-jar', jarName], stdin=PIPE, stdout=open(outputFile, 'w'), stderr=PIPE)
for ln in strInput:
process.stdin.write(ln.encode())
process.stdin.close()
process.communicate()
process.wait()
process.kill()
這個做法一直沿用了下去。對於每條指令的測試,無論一三單元,本人都是通過一個個單獨編寫的函數進行的,同時再用一個函數來生成所有的輸入,這樣可以便捷的更改測試數據。
如:
def add_person():
···
def add_relation():
···
def query_value():
···
def getInput():
for j in range(5):
add_person()
for j in range(10):
add_relation()
···
從測試的理解方面,兩個單元中均採用了手動構造+在給定範圍內的隨機測試,從測試範圍來看,均有從單元測試到複雜測試的過程。其中第一單元中“單元”的劃分為每個運算符,針對它的測試本人做的極為有限,基本是手動輸入一些可能發生的複雜情況(比如-1**0
)。而第三單元中這個“單元”的概念被我放大到這一段幹了同類型事情的指令,比如造人+造組+組裡加人+造紅包信息+發紅包+查詢錢數諸如這樣的組合。但這樣的測試也需要註意順序,比如先測好基本的功能,如group加人刪人是否正常,再進行一個個功能單元的測試,如收發紅包發表情發群公告等,最後再將其融合在一起,進行大雜燴的測試。同時,由於完全隨機的效率低下,在第一單元中就遇到了在2k次中僅出現一次錯誤的情況,所以在第三單元中本人也改變了隨機測試的思路,變為半隨機的樣子——手動控制人的id,隨機其他數據。這樣明顯提高了數據的強度,在自測和互測中都起到了非常好的作用。
在互測中,本人也學習了閱讀他人代碼併發現錯誤的能力,雖然這種錯誤僅限於字元串處理或者一些程設方面的錯誤,其他的問題還是需要藉助評測機的幫助才能被看出來。
所以總結來看,本人通過四個單元的評測,學習了測試的步驟和方面,併在自己的實踐中不斷拓展測試的方面,提高數據的有效性,向覆蓋率高的測試方向發展。
五 / 課程收穫
pre:進行了java的入門,並認識了非常典型的面向對象的例子,通過實踐體會了繼承、實現、關聯等關係,同時認識了UML類圖這樣的架構表述方式。
第一單元:運用pre中學習的基本知識,對錶達式進行抽象,利用在training里學到的遞歸下降方法實現表達式的解析。
第二單元:通過對電梯的安排,瞭解線程安全的實現方式(加鎖,加讀寫鎖,加sychronized修飾符,採用阻塞對了等),學習不同的設計模式(如創建型模式中的單例模式、工廠方法模式,結構型模式模型中的組合模式,行為性模式中的觀察者模式,以及在23中基本模式之外的生產者消費者模式)。對架構能力的要求為四個單元中最高。
第三單元:閱讀JML,學習JML這種語言的語法和優勢,在構建社交網路的過程中複習圖論的演算法併在java中編寫數據結構,學習java中異常的拋出與處理。JML具有非常明確清晰的需求描述,使得本人可以快速得到架構與實現方法。
第四單元:將modeling language從Jml上升至Uml,處理Uml類圖、順序圖和狀態圖三種常用繪圖方式的解析方法,更加深刻的體會如何拆分一個任務,並安排需要的對象。
六 / 改進建議
-
希望可以在每次上機後給出上機測試的答案,雖然這種事情也可以通過問同學解決,但如果有更便捷的,能讓所有人都獲得答案的方式,看起來會更方便一些。這樣本人才能更好的意識到自己在某些地方對代碼理解上的偏差,特別是後面的三四兩個單元,不是程式能跑就說明寫的沒有問題,而且基本都是填空題,如果沒有標準答案的話,容易產生一些誤解。
-
研討課的分享可以擺脫“上一次作業我是如何實現的”這種話題(除非是非常精妙特殊的實現方式),畢竟大家都已經寫完了,而且有互測的代碼進行觀看,在組內討論的環節進行分享就夠了,再進行這樣的講解感覺有點耽誤了研討課的時間。
-
在每個單元第一次作業之前發佈一點自願思考的小任務,比如在pre階段接觸一點遞歸下降的思路,在第一單元博客時寫一些比training中的限制多一些的生產-消費者模型等等,當然一定採取自願的形式,稍微降低一點每個單元第一次作業的難度,否則經常到周四的上機後才能意識到應該怎麼寫,感覺時間有點緊張(不過…可能菜是原罪吧)。
-
第四單元的指導書中,有一些文字可能還可以斟酌一下,因為經過第三單元的JML之後,感覺文字表述的二義性還是有點明顯的,閱讀充滿文字的要求突然覺得陌生了起來(當然,也有可能是本人理解能力的問題)。
-
討論區有時翻起來比較混亂,問題和助教回答混在一起,不知能否改進為每個回答都顯示在問題緊下方的形式,這樣不容易看漏也比較方便閱讀。