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的基础打扎实,写出漂亮稳定的好程序。

相关文章
相关标签/搜索