(1)第一次做業:
第一次做業的設計,如今看來真是雜亂不堪,徹底是面相過程式編程,只不過是套了一個JAVA的殼子而已,存在不少的問題。
第一次做業的題目是求一個簡單多項式的導數。全部的表達式都是形如\(f(x) = \Sigma a*x^c\)這種形式。這個多項式是一個字符串類型,固然咱們首先應該判斷其是否合法。個人思路是首先經過正則匹配檢查是否存在不合法的空格。java
String pattern1 = ".*[+-]\\s*[+-]\\s+\\d.*"; String pattern2= ".*[\\^]\\s*[+-]\\s+\\d.*"; String pattern3 = ".*\\d+\\s+\\d+.*";
上述的三個表達式分別判斷了是否存在表達式加法時的帶符號整數之間的非法空格,數字與數字之間的非法空格,以及指數後的帶符號整數的非法空格。在判斷不存在這樣的非法空格以後,爲了方便後續的項的分離,咱們將字符串中的全部空格和\t製表符全都刪去。
而後咱們對整個字符串進行合法性的檢驗,檢查其每一項是否都屬於\(f(x) = a*x^c\)這種形式。這裏的正則表達式以下。程序員
pattern1 = "([+-]{0,2}(((\\d+[*])?[x]([\\^]([+-])?\\d+)?)|(\\d+)))";
咱們這裏須要注意,由於其是貪婪性匹配,每次都去匹配知足上述模式的最大字符串,所以當字符串巨大時可能會存在爆棧的狀況,所以咱們調用Pattern Matcher裏的方法將其分紅一個個小的GROUP,並獲得每一個表達式的係數以及指數,並將其存在多項式類中正則表達式
下面是第一次做業的類圖
編程
第一次做業的失敗主要是把全部的函數方法都寫在了一個類裏面,不管是數據的讀取,仍是表達式的構造,以及最後的輸出,都是在一個Main類裏,致使這個類的長度達到了幾百行。這是明顯違反了OO的設計準則的。正確的設計方法應該是首先構造一個讀取類,這個類專門用來讀取數據,而且存儲咱們的字符串。而後再寫一個判斷類,來判斷咱們的字符串是否合法。再經過一個分離類,將咱們的字符串進行分離,將分離出來的數據存儲在咱們的多項式類中,最後再經過輸出類來進行數據的輸出。這樣每一個模塊功能明確,而且當往後增長需求的時候,大的模塊不須要變更,只需在各個類中添加或者修改方法便可。
(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)
SourceFile | Total LinesSource Code | Source Code | Comment Lines | Blank 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在於沒有處理指數或者係數過長可能拋出的異常,沒有意識到助教和老師在做業指導書裏的提示,這個問題在我理解了BigInteger類以後獲得了較好的解決。而且在checkStyle風格檢查的時候,沒有按照規範要求的行數以及縮進。還存在表達式第一項若是爲數字的話前面的符號的個數的問題。
第二次
第三次做業
經過第一個單元的學習,已經基本掌握了各類java類的用法,也理解了面向對象的設計思想。瞭解了繼承與接口的原理。可是在使用上還存在不熟練的時候。但願在往後進行多線程學習以前,可以把java的基礎打紮實,寫出漂亮穩定的好程序。