OO第一次单元总结
第一次總結性博客
16071070 陳澤寅 2019.3.23
一、第一單元所學總結
- 首先先來總結一下第一單元我所學到的知識以及所感所悟。第一個單元,是我第一次接觸JAVA語言,并且再使用了幾次之后,就被這門語言的獨有的魅力以及簡便的用法所深深吸引。下面我從三個方面來簡單闡述一下我對于JAVA相比較于c語言的優勢。
- (1)從架構上來說,java的設計思路是不同于c的,它是一門面向對象的語言,我們的思維從熟悉的過程式編程語言轉移到了對象思維上來。這樣的思維的好處是,我們可以將一個大的問題分成很多個小的類去進行處理。如果說過程式編程是一個龐大的整體,而函數是其一個個功能的分布,那么在java里類就是實現各個子模塊的實現者。在java的面相編程思維中,類的設計秉持高內聚,低耦合的思想。即在每個類的內部只關心自己類的操作,而不去關心其他類的事情。這樣的好處是,我們把整個過程細化成很多個類去實現,每個類只需實現自己的功能,而不需關心其他類的功能。這樣方便程序員在寫每個模塊的時候不需考慮當前類對其他類的影響,并且方便進行單元測試以及問題的發現。同時當某個需求發生改變時,只需更改相應的類,而不需去修改其他相關的類。因為類與類之間的關系是低耦合的。這樣方便日后的維護與調試。
- (2)從設計安全性的角度來說,java在大型項目開發的時候更加安全。因為每個類的屬性都是private的,其他類不能直接訪問當前類的私有屬性,因此無法直接對屬性的值進行修改。這是安全的,因為其他類可能并不知道這個類的設計原則,若直接修改類的屬性可能導致bug的產生。java針對這種情況提供了public方法,其他類可以調用public方法去實現類屬性的修改,而一些修改的限制都寫在方法中,因此其他類無需知道這些細節,并且這些public方法也保證了類屬性數據的安全性。同時java還提供了接口的思想,即很多不同的類為了實現某個接口,就能實現類與類之間的聯系。這樣就大大增加了程序的可擴展性和可移植性。
- (3)java還有一個很大的優勢就是其寫法相當簡便,相比于c它提供了大量的內置函數包以供調用,比如其String類,ArrayList類,HashMap類等等。還有一些sort,find函數,這些函數都是經過優化的方法,省去了程序員一些復雜地基本操作,使程序可讀性增強。
二、多項式求導程序設計說明
(1)第一次作業:
String pattern1 = ".*[+-]\\s*[+-]\\s+\\d.*"; String pattern2= ".*[\\^]\\s*[+-]\\s+\\d.*"; String pattern3 = ".*\\d+\\s+\\d+.*";
第一次作業的設計,現在看來真是雜亂不堪,完全是面相過程式編程,只不過是套了一個JAVA的殼子而已,存在很多的問題。
第一次作業的題目是求一個簡單多項式的導數。所有的表達式都是形如\(f(x) = \Sigma a*x^c\)這種形式。這個多項式是一個字符串類型,當然我們首先應該判斷其是否合法。我的思路是首先通過正則匹配檢查是否存在不合法的空格。上述的三個表達式分別判斷了是否存在表達式加法時的帶符號整數之間的非法空格,數字與數字之間的非法空格,以及指數后的帶符號整數的非法空格。在判斷不存在這樣的非法空格之后,為了方便后續的項的分離,我們將字符串中的所有空格和\t制表符全都刪去。
pattern1 = "([+-]{0,2}(((\\d+[*])?[x]([\\^]([+-])?\\d+)?)|(\\d+)))";
然后我們對整個字符串進行合法性的檢驗,檢查其每一項是否都屬于\(f(x) = a*x^c\)這種形式。這里的正則表達式如下。我們這里需要注意,因為其是貪婪性匹配,每次都去匹配滿足上述模式的最大字符串,因此當字符串巨大時可能會存在爆棧的情況,因此我們調用Pattern Matcher里的方法將其分成一個個小的GROUP,并得到每個表達式的系數以及指數,并將其存在多項式類中
下面是第一次作業的類圖
(2)第二次作業:第二次作業在第一次作業的基礎上增加了\(sin(x)以及cos(x)這兩種冪函數\),并且在每個表達式中允許存在兩個因式相乘的形式。由于第一次作業的偷懶設計,導致第二次作業的架構需要重新設計,但是在做完第三次作業之后發現第二次作業的設計依舊是有很大的弊端。
第二次作業的多項式的形式是 \(f(x)=\Sigma a*sin(x)^b*cos(x)^c*x^d\)。項里可以存在乘法,這就需要更改之前的表達式的分離的方法。首先我先更改了多項式的存儲結構,運來的多項式里只包含x的指數和系數。現在加入了\(sin(x)和cos(x)\)的指數。最后得到一個項的list,并且根據相應的公式進行求導。求導的公式是
\(a*l*x ^ (a - 1)*cos(x)^c*sin(x)^b + b*l*x^a*cos(x)*cos(x)^c* sin(x)^(b - 1) - c*l*x^a*cos(x)^(c - 1)*sin(x)*sin(x)^b\)這次作業在上一次的基礎上更新正則表達式的匹配樣例
String patternHead = "[+-]{0,2}(([+-]?\\d+)|(" +"[x]([\\^][+-]?\\d+)?)" +"|([s][i][n][\\(][x][\\)]([\\^][+-]?\\d+)?)" +"|([c][o][s][\\(]" +"[x][\\)]([\\^][+-]?\\d+)?))([*](([+-]?\\d+)|([x]([\\^][+-]" +"?\\d+)?)|" +"([s][i][n][\\(][x][\\)]([\\^][+-]?\\d+)?)|([c][o][s][\\(]" +"[x][\\)]([\\^][+-]?\\d+)?)))*";通過這個表達式去得到一個個項,然后通過split
Pattern pattern = Pattern.compile("[+-]{0,3}[0-9]+"); Pattern pattern = Pattern.compile("([+-]{0,2})[x]([\\^]" +"?(([+-]?)(\\d+)))?"); Pattern pattern = "([+-]{0,2})sin[\\(]x[\\)]([\\^]([+-]?\\d+))?"; Pattern pattern = "([+-]{0,2})[c][o][s][\\(]x[\\)]([\\^]([+-]?\\d+))?";
函數將\(*\)號分開得到一個個因式,再通過因式的匹配樣例分別得到了項的系數,\(x的指數,sin(x)的指數,cos(x)的指數\),然后存入我們的結構體中。最后通過上述求導公式對每個項進行求導,并且將相同系數的項合并。
類圖如下可以看到其實第二次設計依舊沒有秉持好的設計原則,雖然將不同的功能寫在不同的方法里,但是沒有實現類的分類,這里好的設計應該是sin(x),cos(x),x單獨分類,然后進行求導,以及輸出。然而這里混在了一起,導致main方法十分龐大,修改一個地方會導致很多方法都需要修改。因為代碼之間的耦合度非常高,并且幾乎所有的操作都是寫在Poly里的靜態方法,導致第三次作業又需要進行大規模的重構。
(3)第三次作業
這次作業是我三次里認為還比較滿意的一次作業,因為這次的題目比較復雜,因此我覺得不能再像前兩次作業那樣急于編寫代碼,因為這樣會導致代碼紊亂,最后難以找bug。因此在動手寫代碼之前,我仔仔細細地參照面相對象編程的思想,對第三次的題目進行了思考,先把類和接口設計好再進行動手編寫代碼,果然想清思路之后再下手,寫起來快并且最后的bug也少了,代碼思路非常清晰。簡單分析一下這次的作業,這次的多項式求導不僅延續了之前的項的思路,還添加了嵌套求導的規則。即
表達式 = 項{+項}項 = 因子*{因子}因子 = (表達式)|因子也就是類似\(sin(cos(x))\)這種嵌套的求導。我知道再延續以往的面相過程求導肯定是行不通的了。因此這次的設計對每一步進行了細致的類的劃分。類圖如下。
下面來講一下我的做法,首先我這次劃分了很多個類,常數類、x項類、cos項類、sin項類、指數項類、加法類、乘法類這些類,這些類都實現了一個求導的接口,也就是說所有求導的對象都是另一個可求導的對象,比如說指數類里,指數的底數也是一個實現了求導的類,這樣就很好地體現了分類的思想,并且在指數這個類里,我只需管指數函數是如何求導的,而不需要管其底數是什么,因為底數實現了求導接口,底數也會自動去調其覆寫的求導方法去對底數進行求導。這樣就使我們的程序顯得很簡單。
這里的加法和乘法類就是指兩個實現了求導接口的對象相乘進行求導,我們只需關心乘法的求導是怎么樣的,而具體對象的求導,放在具體的對象的求導里去完成,這樣就真正實現了低耦合的思想。具體的接口的代碼如下:
public interface Derive { public abstract Derive derive();//求導函數public abstract void print(); }然后乘法里實現的求導接口的求導方法如下。
public Derive derive() {ArrayList<Derive> addList = new ArrayList<Derive>();for (int i = 0; i < this.list.size(); i++) {/** 根據幾個函數連乘的求導法則進行求導* result = (ui' * (u1*u2*....un)/ui)的和*/ArrayList<Derive> multList = new ArrayList<Derive>();for (int j = 0; j < this.list.size(); j++) {if (i != j) {multList.add(this.list.get(j));}}multList.add(list.get(i).derive());Mult mult = new Mult(multList);/*** 這條multList 就是Add鏈中其中的一條**/addList.add(mult);}/*** 至次為止, addList是由好幾個Mult類構成*/return new Add(addList);}我們可以看到,對于\(f(x) = h(x)*g(x)\)的求導,只需關心\(f(x)'=f(x)'g(x)+f(x)g(x)'\)即可,而不需關心\(f(x)'和g(x)'\)是什么,因為\(f(x)和g(x)\)都是已經實現了求導方法的對象,在他的類里會調用自己的求導方法進行遞歸求導。
在明確了類的框架結構以后,我們再想辦法對字符串進行處理,我一開始嘗試原來的正則文法匹配的方法,但是發現自己不明白如何去產生上述的表達式里嵌套因子的方式,但是我發現,這個形式和我們之前學過的編譯原理的遞歸下降分析法類似。于是我采用相同的思想先寫了一個簡單的詞法分析小程序,然后構造了expr(),term(),factor()三個子程序來對字符串進行讀取。這樣就能實現程序的因子里嵌套表達式的形式了。下面舉一個簡單的表達式的例子。
/*** 自頂向下的表達式子程序*/ public static Derive expr() {/*** 表達式 = 項 {+項}*/ArrayList<Derive> exprList = new ArrayList<Derive>();Derive term1 = term();exprList.add(term1);if (!checkExprTail()) {System.out.println("WRONG FORMAT!");System.exit(0);}while (sym.equals("Add") || sym.equals("Minus")) {headFlag = 1;term1 = term();exprList.add(term1);}if (!checkTail()) {err();}return new Add(exprList); }這樣在后續的調試過程中我可以單獨根據每種形式的求導來找問題,就能很快地發現是哪個求導過程發生了問題,簡明扼要。
三:程序結構分析
各類代碼總規模:(statistic)
SourceFileTotal LinesSource CodeSource CodeComment LinesBlank Lines Derive.java 8 4 3 1 X.java 10 8 0 2 Constant.java 25 19 0 6 Sinx.java 28 18 4 6 Cosx.java 28 20 3 5 Mult.java 52 30 13 9 Add.java 54 40 3 11 Degree.java 61 36 13 12 Poly.java 390 345 21 24 Total 658 522 60 76 類的屬性方法個數
類屬性方法 Derive.java 0 2 X.java 0 2 Constant.java 1 3 Sinx.java 1 3 Cosx.java 1 3 Mult.java 1 2 Add.java 1 2 Degree.java 2 5 Poly.java 8 14
四:程序優缺點
優點:
1:將復雜的多項式求導分成諸多形式的類,每個類只需注意自己的求導形式,具體的求導規則由各個實現求導接口的類去完成。 2:采用遞歸下降子程序法,使字符串的處理比較容易理解,并且不容易出錯。 3:使用了接口的思想,方便可擴展性。 4:實現了高內聚低耦合的思想,使每個類和方法盡量能干的事情少,各自之間互不影響。缺點:
1:在處理項的連乘的時候可能會出現爆棧的情況。2:沒有做完備的可能發生異常的情況的統計與測試。3:單元測試不夠完備,Main類的設計還過于冗雜。4:存在大量的if-else語句,不夠精煉,存在代碼復用比較嚴重。5:輸出的時候如果嵌套層次太多,會導致大量的()產生,很難進行優化。
五:程序的Bug分析
第一次
第一次的bug在于沒有處理指數或者系數過長可能拋出的異常,沒有意識到助教和老師在作業指導書里的提示,這個問題在我理解了BigInteger類之后得到了較好的解決。并且在checkStyle風格檢查的時候,沒有按照規范要求的行數以及縮進。還存在表達式第一項如果為數字的話前面的符號的個數的問題。
第二次
- 第二次的第一個小bug是未考慮Scanner異常輸入時拋出的異常會使程序crash,這里只需要在輸入的地方加上try-catch的異常處理即可。
- 第二個bug是在去除空格的時候沒有吧制表符\t一起去除,沒有重視空格space與制表符在ASCII碼上不同的本質區別。
- 第三個bug是在輸出的時候,我是按照不管指數系數為不為0或1,全部將其按照\(a*x^b*sin(x)^c*cos(x)^d\)的格式輸出,然后再對字符串進行處理,去掉"+1","1","^1"這些,但是忽略了完備性,如果一個指數恰好是 \(y^12\),那么去掉\(*1*\)之后就變成了\(y2\),這明顯是錯誤的。錯誤的原因就是沒有對類進行細分,如果按照第三次作業的方式對每個函數進行分類輸出就會簡單很多,可以分別判斷系數、指數為0為1的情況,就可以省去大量的if-else并且保證程序的正確性。
第三次作業
- 沒有考慮到輸出的時候\(sin(2*x)\)這種錯誤的輸出格式帶來的問題。
- 一開始沒有做左右括號匹配的處理。
六:自我評價與寄語
通過第一個單元的學習,已經基本掌握了各種java類的用法,也理解了面向對象的設計思想。了解了繼承與接口的原理。但是在使用上還存在不熟練的時候。希望在日后進行多線程學習之前,能夠把java的基礎打扎實,寫出漂亮穩定的好程序。
轉載于:https://www.cnblogs.com/somnus-chen/p/10591953.html
總結
- 上一篇: Python中xlrd模块解析
- 下一篇: Mockito 的使用