學習面向對象這門課程的後的第一單元做業,主線是多項式求導,三次做業層層推動,由單一的冪函數求導,到冪函數和三角函數的複合求導,最後再到兩種函數的嵌套求導,由兩個類到重構後的十幾個類,我逐漸對面向對象的思想有了更深一步的理解,對結構化的設計也有了更加深入的體會。正則表達式
實現僅含冪函數和常數的多項式求導,數據長度上限1000,性能上要求結果越短越好(即化簡到最簡),保證輸入數據合法。設計模式
完成本次做業時,因爲擴展意識不足,採起了僅爲解決當前問題的設計模式,包含兩個類Poly和PolyComputer,其中Poly類用一個HashMap來存儲多項式每一項的係數和指數,使用BigInteger來管理系數和指數,用PolyComputer類讀入字符串並進行處理,並在其中直接對求導結果進行輸出,如下是個人類圖分析svg
優勢函數
因爲保證輸入數據合法,在dispose處理字符串時先去掉空格並對先連的正負號進行合併,使得在parsePoly中利用正則表達式解析字符串更加簡便工具
在用addTerm增長項時利用DegExist判斷當前指數的項是否存在,存在就直接合並,作到了及時合併同類項post
在printDeva輸出時對係數和指數爲0、±1時進行了特判,使輸出結果達到較簡的形式性能
缺點學習
在輸出時的特判狀況較多, 易出錯測試
直接對多項式進行求導,沒有存儲求導結果,使得printDeva與其它模塊的耦合性過強,代碼質量低優化
因爲前期對字符串進行處理後再進行正則判斷,沒法適應後面須要判斷格式的改變
未對函數設置專門類,致使代碼在遇到多種函數複合求導時須要重構,擴展性低
本次做業中,因爲我在判斷輸出是失誤,致使>1時才輸出指數,由於這一個失誤下的大bug,在強測及互測階段我被hack得很慘。固然我也進行了深入的反思,因爲第一次做業對規則還不是特別熟悉,本身在課下並無作好充分的測試,才致使了這一bug苟過中測殘留到強測及互測階段。
在對別人的代碼進行測試的過程當中,我通常是構造邊緣數據進行測試,如係數及指數爲±一、0,連續幾項相同指數須要合併,常數單一項等的狀況,而後再讀別人的代碼進行鍼對性測試。最後,也成功幾回hack到了別人。
在第二次做業中,增長了三角函數的求導及f(x)*g(x)的複合求導形式,但三角函數因子只能爲sin(x)和cos(x),同時,也要求對輸入數據進行格式判斷。
在實現本次做業過程當中,因爲上一次代碼擴展性太差,且考慮到下次做業會更加複雜,我選擇了及時重構。重構時的思路主要分爲如下三個方面:
一、結構化層次關係的設計
首先,我定義了Factor類,設置係數和指數屬性,在裏面定義了一些因子共有的特徵及加和乘的運算,而後使冪函數PowFunc和三角函數TriFunc 繼承自Factor類,在每一個子類中重寫derivation()和toString()方法,在三角函數內設type屬性存儲三角函數類型。
而後,考慮到本次做業的特徵,每項最多由冪函數*正弦函數*餘弦函數
的格式組成,故在Term類僅設置三個因子對象作爲屬性,並對無參數的構造方法初始化爲係數爲1,指數爲0的形式,同時,爲了將每項係數合爲一塊兒,我將冪函數的係數默認爲項的係數,在每一項被加入到多項式時利用combineCoef()方法將三個函數的係數合併。
最後,將原來的Poly類內由存每項的係數和指數,改成存多個Term的容器,而且在addTerm()時利用isCoefZero()及時刪去了零項。
總之,在對象的建立上,我總體實現了由factor→term→poly的層次結構。
關於求導方面,我利用三層結構的迭代求導,在Term類內的derivation()方法中根據f(x)*g(x)的求導規則進行特殊書寫。
二、輸入數據的格式判斷及解析
對於Wrong Format!判斷,我自定義了InputException()的例外,並在其中設置printError()方法,在檢測到非法格式時拋出例外。在Main類中進行try-catch的捕捉。
因爲上次做業我先對錶達式進行處理,致使沒法判斷輸入數據格式,在本次做業中,我將原來的PolyComputer類改成StringHandler類,進行輸入數據的判斷和處理,並在正則表達式中補入了空格,先根據正則表達式檢測數據格式,而後用dispose()方法對錶達式進行處理,最後以項爲單位解析表達式,存入一個Poly對象中。
三、關於優化
此次的做業若是利用各類三角函數的公式,其實有不少點能夠進行優化,可是不少時候實現並不容易,且容易引起不少未知bug,何況不少優化規則的實例出現機率很小,故我最後只在如下三個方面進行了優化。
sin2(x)+cos2(x)=1
同類項合併
x**2→x*x
最後,我重構後的代碼結構類圖分析以下:
其中,StringHandler的循環複雜度較高,但由於涉及到字符串的解析,因此我認爲是沒法避免的。
優勢
結構層次較爲清楚,有必定的可擴展性
各種功能簡單,不易出現bug
實現了簡單的優化
缺點
對Term類的屬性處理不當,致使後面在增長多中因子時須要重構,改成存儲Factor的容器
在Poly中進行優化時涉及到多個類的方法調用,耦合度較高
在StringHandler處理字符串時,生成項判斷能夠利用工廠方法來進行解耦,也會使思路更加清晰
因爲吸收了上一次的經驗,以前進行了自我測試,並寫了自動化測試工具進行測試,在強測及互測階段,個人程序並無被hack到,但我也知道它可能在某個神奇的地方仍存在bug
與此同時,在測試別人代碼時,我偷懶使用了自動化測試工具, 而且因爲當時在忙一些其它的事情,我沒有時間去分析完每一個人的代碼,本身構造的一些測試點也沒有hack到別人,最後自動化測試也不爭氣,我體會了惟一一個和平的互刀環節。
本次做業主要增長了三角函數的嵌套求導,並增長了表達式因子
一、結構層次關係的設計
因爲增長了表達式因子和嵌套的三角函數,我增長了NestFactor和ExprFunc兩類,其中ExprFunc繼承自Poly類,將原來的Factor父類改成存儲變量因子的VariableFactor,並使全部的因子實現Factor接口,實現求導、加乘等一系列因子的基本操做。
在處理嵌套因子時,因爲嵌套的求導規則是對每層進行求導並相乘,在這裏,爲了防止爆棧,我在NestFactor用了一個Factor的容器,從外向內存儲嵌套每層因子,求導時只需對容器的每一項進行求導,對於三角函數括號內的部分在TriFunc中定義factor字符串直接進行存儲,在輸出時代替原來x的位置便可
具體的UML類圖以下:
二、輸入數據的格式判斷及解析
在輸入數據的格式判斷,因爲這次爲含遞歸的正則表達式,不能簡單用正則表達式直接判斷,在此,我新建了一個FormatCheck類,對輸入表達式進行遞歸判斷,並專門寫一個findRightBlacket()的靜態方法,返回與當前字符串最左側括號所匹配的右括號位置。
在解析表達式時,我新建了一個PolyHandler類,將表達式以項爲單位傳入Term中,循環填充一個Poly類的對象,同時,爲了減小類之間的耦合度,我將格式檢查也在這裏進行。具體的關係以下圖:
在解析表達式時,吸收上一次的經驗,我新建了FactorFactory的工廠類,把解析出的因子表達式傳入生成相應類型的Factor對象
在下面類度量中,能夠發如今,還是在含表達式的解析的類循環複雜度比較高
三、關於優化
實現了部分同類的因子和項之間的合併
在NestFactor中,經過重寫的toString()對嵌套因子內部的字符串進行循環替換,簡化了含前導0、符號多餘以及因子之間可合併的地方
x**2→x*x
優勢
表達式的解析分爲幾部分在類的內部執行,減小了表達式解析的循環深度
迭代求導部分思路比較清楚
嵌套因子的處理較爲簡便
缺點
因爲三角函數可被歸爲ExprFunc和TriFunc兩類,同類項合併時很差判斷
因爲表達式因子的存在,使得因子求導的返回必須是Poly類對象,對於VariableFactor類,其實返回Factor類就夠了,但還須要把他們一步步轉成Poly類,以爲較爲冗餘,但也沒有好的辦法解決
在對求導結果進行復合時,須要分類處理,不能作到很好的歸一化,不然會出現神奇的bug,感受有點麻煩,應該還有解決辦法
此次做業太過複雜了,果真最後出現了一些很神奇的邊緣bug,雖然很好改,但我被hack得很慘
在表達式因子求導獲得Term類對象後,我將它toString()的結果傳入了Term中進行新的一項的解析,但因爲我將x**2轉化成了x*x,會致使傳入sin(x*x)類不合法因子,而我並未對此類因子進行解析,因此最終會出現RuntimeError
在因爲優化, 輸出時可能會有sin(x*x)類不合法輸出格式的因子
因爲我在TriFunc類裏的toString()方法內直接選擇對factor爲0的因子輸出爲0,致使有cos(0)時會出現錯誤
在測別人的bug時,我針對sin(0)**0
,cos(0)
等類型的易錯點設置了測試樣例,果真也hack到了一波別人,同時也測試了多層括號嵌套、連續符號判斷等狀況