OO第一次总结
一.基于度量的程序結構分析
在進行分析之前,先解釋一下以下幾個縮寫:
LOC:代碼行數
CC:圈復雜度,反映了程序中if/while等判定條件的數量,越高意味著代碼越可能質量低且難以測試、維護。
PC:方法參數個數
NOF:類的屬性個數
NOPF:類的public屬性個數
NOM:類的方法個數
NOPM:類的public方法個數
NC:類的子類個數
DIT:類的繼承樹深度
LCOM:類中內聚度的缺乏,越大意味著內聚度越差。
FAN-OUT:某個類引用其他類的次數
FAN-IN:類被其他類引用的次數
?
1、第一次作業
(1)設計思路
第一次作業使用了兩個類:PolyItem用于管理多項式的每一項;PolyDerivation通過調用PolyItem類實現多項式合法性判定以及求導。在判斷輸入表達式合法性的同時,運用arraylist結構構造了一個每一項為PolyItem類的動態鏈表,對這個鏈表進行求導,最終得到多項式的導數,由于每一個PolyItem類維護系數和指數兩個屬性,因此在合并同類項的過程中可以通過判斷指數是否相等而進行合并。
(2)結構分析
類圖如下所示:
?
可見,PolyItem僅包含了對某一項求導、合并同類項的簡單方法,而PolyDerivation則比較冗長的包含了多種處理。程序度量如下:
?
顯然,與PolyDerivation相比,PolyItem中的方法行數少、復雜度低,比較容易測試,而PolyDerivation中方法CC有的高達11,這給程序的測試帶來了一定困難。此外,PolyDerivation類的代碼長度過長,達258行,表明類中可能存在比較多的冗余代碼。由于第一次作業寫的時候剛剛接觸JAVA,基本是按照C的套路寫的,類的劃分并不好,也并沒有用到繼承(當時也并不知道繼承是啥),因此程序層次并不深,卻也導致了類中的內聚度比較好(大概是因為寫成了C吧)。
?
2、第二次作業
(1)設計思路
第二次作業使用了三個類:PolyItem與第一次作業類似,用于管理多項式的某一項;PolyDerivation也與第一次作業的作用一致,用于識別整個多項式并完成求導;新增的Derivation類用于單獨處理求導、合并同類項操作,目的是將不同類別因子的求導方法與多項式類分離。第二次作業的思路與第一次作業沒有太大的區別,僅僅是在PolyItem類中多維護了sin(x)、cos(x)的系數這兩個屬性,合并同類項的方式也比較類似。
(2)結構分析
類圖如下:
?
每個類及類中方法的度量如下:
?
可見,仍舊是多項式類的代碼行數、方法行數、屬性個數、復雜度比較高,其余兩個類則比較簡潔。由于求導類僅用于處理每一項的求導,項類則只用于管理合并同類項等粒度到項的操作,類內的內聚度比較好(由LCOM值較低可得),類間的耦合度較少。但是在第二次作業的時候,我顯然沒有領會到老師所提的代碼重構的真正意圖,我的這種架構在遇到第三次作業的之后完全崩潰,這告訴我代碼的可擴展性也是很重要的,雖然在一開始設計時,我往往更關注本次作業所要完成的任務,而忽略了如何支撐更多、更復雜的功能。
?
3. 第三次作業
(1)設計思路
第三次作業明顯比前兩次難了很多,由于前兩次作業我都沒有考慮支持嵌套的問題,也沒有使用繼承、接口,我進行了完全的重構。如果說第二次作業是在第一次作業的基礎上進行了擴展的話,第三次作業則是完完全全的重寫。這次作業不僅在處理嵌套問題上難住了我,在最開始的多項式識別階段就令我感到頭疼。因為正則表達式不能支持遞歸識別,我最終采用了類似編譯器的遞歸下降子程序處理方法,并在識別的過程中顯式構造了一棵表達式樹,這棵表達式樹的葉子節點是形如x^2、sin(x)^2、cos(x)^2、常數這樣的基本因子,樹的中間節點是加、減、乘、嵌套等運算,由于在嵌套函數f(g(x))的求導過程中,sin(x)^2與sin(factor)^2的求導方式沒有本質的區別,因此,我將其合并成了同一類。表達式樹上的每一類結點都維護一個屬于自己的求導方式,樹建立之后,通過調用根節點的求導方法即實現整個表達式的求導。
(2)結構分析
這次涉及的類比較多,類圖如下所示:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?
類的度量如下:
?
可見,使用繼承之后,類的屬性和方法有了明顯的減少,但是有的類里出現了內聚度降低的情況,這表明類的劃分并沒有符合高內聚低耦合的原則。盡管使用了繼承,但是繼承樹的層次相對比較小,大多為1層,結構比較簡單。盡管第三次作業的架構難以實現優化的目標,但是通過將求導這個大問題拆成不同形式求導的小問題,簡化了程序的結構,只要獨立地思考每個類所要完成的功能,再最后進行簡單的組裝,就能實現求導功能。在這里,想簡要的說明一下第三次作業中我關于優化的設計,因為并沒有從整體上考慮優化的問題,我僅僅是在求導過程中進行了比較細節的優化,例如:在形如x^2這樣的基本函數的求導過程中,如果系數是1,則直接得到x而非x^1;在加、減、乘、嵌套的求導時,如果乘法的因子中包含0,則直接得到0,而不進行額外的運算,如果涉及的各因子、項都是常數,則可以直接運算的到結果而不需要表達式輸出等。盡管這些優化效果可能不是特別顯著,但也起到了一定的作用。
?
?
二.程序中的bug分析
這三次作業中,前兩次作業的設計都比較簡單,主要的思想是在判斷多項式合法性的過程中,構造了由項類組成的動態鏈表,再進行求導、合并同類項以達到優化的目的。第三次作業則采用了樹的結構,在判斷多項式合法性的過程中構造一棵表達式樹,通過對根節點求導得到整個表達式的導數,并在每個類所維護的求導方法中進行力所能及的優化。顯然,表達式樹的結構要更具有可擴展性,并且能支持嵌套結構,且在實現的過程中,類之間的耦合度更低,只要實現好每個子類的求導,再將樹構建好,即可完成整個表達式的求導。
由于表達式樹的構建相對復雜一點,也比較容易產生bug。在構建的過程中,我主要遇到的是有關“不加括號導致運算順序出錯”的問題。在中間結點的求導過程中,由于左右兩棵子樹可能是因子、項、表達式中的任意一種,因此需要額外注意加括號的問題。例如:(sin(x) - x*cos(x))' = cos(x) - (cos(x) - x*sin(x)),如果不加括號,就會產生運算錯誤。
- 在減法求導過程中,(f(x)-g(x))' = f'(x) - g'(x),為保證運算的正確性,減號右邊需要加括號。
- 乘法求導時,(f(x)*g(x))' = f'(x)*g(x) + f(x)*g'(x),由于項的性質,f(x)與g(x)無需加括號,但是f'(x)和g'(x)可能出現“一項變兩項”的情況,因此,需要加括號。
- 嵌套求導,f'(g(x)) = f'(g(x)) * g'(x),由于外層的f(x)函數是基本因子的形式,求導后不需要加括號,但是g(x)求導后可能出現“一項變兩項”的情況,也需要加括號。
?
三.使用對象創建模式
第三次作業中,我應用的主要是繼承,先構建一個基本的樹節點,其中維護左右子樹結點等屬性以及求導、獲取子樹導數值等方法,而其子類則是通過重寫求導方法實現不同函數求導的功能。在了解了工廠模式的相關內容之后,我將第三次作業進行了重構。
重構之后的類圖大致為:
?
?
?
使用工廠模式可以很方便地根據需求創建不同類型的結點,并以求導函數為接口,實現不同類型函數的求導。
?
四. 總結
這三次遞進式的求導作業讓我經歷了“如何寫JAVA”->“如何盡量寫出不像C的JAVA”的過程,每次需求的增加不僅考驗我們需求分析的能力,也考驗了我們設計的能力。在寫代碼之前,先做好設計是很有用的,這不僅可以防止出現邊寫邊改的情況,也有助于后期debug階段更迅速定位bug。
?
轉載于:https://www.cnblogs.com/qrrr/p/10585975.html
總結
- 上一篇: 非常好奇现在闲鱼上的一些买家都是来捡破烂
- 下一篇: 求一个有花的QQ网名。