OO第一次單元總結

第一次總結性博客

16071070 陳澤寅 2019.3.23

1、第一單元所學總結

  • 首先先來總結一下第一單元我所學到的知識以及所感所悟。第一個單元,是我第一次接觸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函數,這些函數都是通過優化的方法,省去了程序員一些複雜地基本操做,使程序可讀性加強。

2、多項式求導程序設計說明

  • (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分析

  • 第一次

    第一次的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的基礎打紮實,寫出漂亮穩定的好程序。

相關文章
相關標籤/搜索