OO_Unit1_Summary


OO第一单元总结

概况

第一单元刚开始接触oo,我犯了一个经典一main到底的错误,反映出了对面向对象的理解还不够透彻。主类中的main方法因为承担了非常多的任务,使用了大量的循环和判断语句,导致看起来非常臃肿,代码的可度性也较低。这点在以后的学习中一定要避免,毕竟学习的目的不仅仅在于完成作业这么简单。

这一单元的作业只在第一次作业中被发现了bug(读取幂函数的底数时没有考虑输入大数的情况),bug较少的原因可能也因为采用的预解析读入模式不需进行复杂的字符串处理。除了正确性,这几次作业的性能分几乎没有拿到,第一次作业压线提交没有来得及做优化,而之后两次作业也因为对容器的用法不熟悉而导致coding中各种错误。要改进的地方实在太多了。

 

第一次作业

主要思路

由于项中仅有常数项与幂次项,因此每个项都可以化简为 a*x**p 的形式,也方便进行同类项的合并、化简与处理。

对于读入数据,共有三种情况:x,'f[0-9]+',数字。每遇到一个新的数字,就new一个Data对象,剩下两类为Expression对象,这样计算展开就转化为这两类的计算。

作业分析

第一次作业花了特别长的时间,一开始准备采取一般读入,想了很久也没有找到合适的方法,最后选择了预解析读入。除了 'x' 之外,这里的expression只有 'f[0-9]+' 所以保存这些表达式就显得格外简单。main函数的臃肿主要是因为处理输入的过程中过于繁琐,如果后续进行重构,我会优先考虑增加一个输入类,分担main方法的工作量。

本次作业的类图如下所示,输入处理在MainClass中实现

本次作业的复杂度分析

methodCogCev(G)iv(G)v(G)
Data.Data(BigInteger, int) 0.0 1.0 1.0 1.0
Data.mulData(Data) 0.0 1.0 1.0 1.0
Data.negData() 0.0 1.0 1.0 1.0
Data.printString() 7.0 1.0 7.0 7.0
Expression.addData(Data) 0.0 1.0 1.0 1.0
Expression.addExp(Expression) 2.0 1.0 3.0 3.0
Expression.getExp(Expression) 1.0 1.0 2.0 2.0
Expression.mulDataExp(Data) 1.0 1.0 2.0 2.0
Expression.mulExp(Expression) 3.0 1.0 3.0 3.0
Expression.negExp() 1.0 1.0 2.0 2.0
Expression.powExp(int) 6.0 1.0 4.0 4.0
Expression.printString() 3.0 1.0 3.0 3.0
Expression.subExp(Expression) 2.0 1.0 3.0 3.0
MainClass.main(String[]) 230.0 50.0 80.0 83.0
Total 256.0 63.0 113.0 116.0
Average 18.285714285714285 4.5 8.071428571428571 8.285714285714286

第二次作业

主要思路

第二次作业在第一次作业的基础上新增了三角函数,由于采用预解析读入,自定义函数与求和函数不需另行考虑(感觉难度大大下降了)

为了同时管理幂函数,我们把原先的Data类变成了Term类,此外增加一个管理幂函数的Data类和管理三角函数的TriData类,Term类由 Data[Tridata]构成,即 a*x**p * sin(Exp)... 的形式。其中sin/cos(Exp)为TriData类处理的内容,因此只需两个Data member一个用来判断sin或cos一个用来存放里面的表达式。

类图如下

通过复杂度计算我们发现有着最大问题的还是main方法,加上了sin和cos的读入导致代码更长了,而且代码的重复率很高,很不oo。

methodCogCev(G)iv(G)v(G)
Data.Data(BigInteger, int) 0.0 1.0 1.0 1.0
Data.getConstant() 0.0 1.0 1.0 1.0
Data.mulData(Data) 0.0 1.0 1.0 1.0
Data.negData() 0.0 1.0 1.0 1.0
Expression.addTerm(Term) 0.0 1.0 1.0 1.0
Term.Term() 0.0 1.0 1.0 1.0
Term.addTriData(TriData) 0.0 1.0 1.0 1.0
Term.getData() 0.0 1.0 1.0 1.0
Term.setData(Data) 0.0 1.0 1.0 1.0
TriData.TriData(Boolean, Expression) 0.0 1.0 1.0 1.0
Expression.getExp(Expression) 1.0 1.0 2.0 2.0
Expression.mulTermExp(Term) 1.0 1.0 2.0 2.0
Expression.negExp() 1.0 1.0 2.0 2.0
Term.negTerm() 1.0 1.0 2.0 2.0
Term.printString() 1.0 1.0 2.0 2.0
Expression.addExp(Expression) 2.0 1.0 3.0 3.0
Expression.subExp(Expression) 2.0 1.0 3.0 3.0
Term.mulTerm(Term) 2.0 1.0 3.0 3.0
TriData.printString() 2.0 1.0 2.0 2.0
Expression.mulExp(Expression) 3.0 1.0 3.0 3.0
Expression.powExp(long) 6.0 1.0 4.0 4.0
Expression.printString() 6.0 1.0 4.0 4.0
Data.printString() 9.0 1.0 9.0 9.0
MainClass.main(String[]) 351.0 63.0 102.0 105.0
Total 388.0 86.0 153.0 156.0
Average 16.166666666666668 3.5833333333333335 6.375 6.5

每个类的循环复杂度如下,可以看出MainCLass已经爆炸了...趁着第四周没有新的作业,更新一下读入方法把

ClassOCavgOCmaxWMC
Data 2.0 6.0 10.0
Expression 2.6666666666666665 4.0 24.0
MainClass 73.0 73.0 73.0
Term 1.5714285714285714 3.0 11.0
TriData 1.5 2.0 3.0
Total     121.0
Average 5.041666666666667 17.6 24.2

第三次作业

第三次作业在第二次作业的基础上几乎没有做任何改动,三角函数的嵌套已经在第二次作业中实现了,自定义函数的嵌套预解析读入后无需再进行考虑了。类图和度量也与第二次作业一致

改进的地方

·增加输入方法

把输入部分分散到各个方法中,可以使得代码的可读性更强,且更有利于后续的迭代工作。

public static void input(String[] strs,Expression exp, HashMap<String, Expression> exps) {
       exps.put(strs[0], exp);
       if (strs.length == 2) {
           strs[1] = strs[1].replace("+", "");
           while (strs[1].contains("--")) {
               strs[1] = strs[1].substring(2);
          }
           if (strs[1].equals("x")) {
               Data data = new Data(BigInteger.ONE, 1);
               Term term = new Term();
               term.setData(data);
               exp.addTerm(term);
          } else {
               Data data = new Data(new BigInteger(strs[1]), 0);
               Term term = new Term();
               term.setData(data);
               exp.addTerm(term);
          }
      }
       else {
           switch (strs[1]) {
               case "neg" :
                   inputNeg(strs, exp, exps);
                   break;
               case "pos" :
                   inputPos(strs, exp, exps);
                   break;
               case "sin" :
                   inputSin(strs, exp, exps);
                   break;
               case "cos" :
                   inputCos(strs, exp, exps);
                   break;
               case "add" :
                   inputAdd(strs, exp, exps);
                   break;
               case "sub" :
                   inputSub(strs, exp, exps);
                   break;
               case "mul" :
                   inputMul(strs, exp, exps);
                   break;
               case "pow" :
                   inputPow(strs, exp, exps);
                   break;
               default: System.out.println("invalid input");
          }
      }
  }

可以看到,修改后的复杂度大大降低了。

ClassOCavgOCmaxWMC
Data 2.0 6.0 10.0
Expression 2.6666666666666665 4.0 24.0
MainClass 6.75 14.0 81.0
Term 1.5714285714285714 3.0 11.0
TriData 1.5 2.0 3.0
Total     129.0
Average 3.6857142857142855 5.8 25.8

·合并同类项

step1:三角函数的compareTo方法和表达式的compareTo方法

    @Override
   public int compareTo(TriData triData) {
       if (this.isSin && !triData.isSin) {
           return -1;
      } else if (!this.isSin && triData.isSin) {
           return 1;
?
      }
       if (this.expression.compareTo(triData.expression) < 0) {
           return -1;
      } else if (!this.isSin && triData.isSin) {
           return 1;
      }
       return 0;
  }
?
    public int compareTo(Expression expression) {
       if (this.terms.size() > expression.terms.size()) {
           return 1;
      }
       else if (this.terms.size() < expression.terms.size()) {
           return -1;
      }
       else {
           for(int i = 0; i < this.terms.size(); i++) {
               if(this.terms.get(i)!=expression.terms.get(i)) {
                   return -1;
              }
          }
           return 0;
      }
  }

step2:编辑一个判断项是否能够合并的equals方法

@Override
   public boolean equals(Object o) {
       if (this == o) {
           return true;
      }
       if (o == null || getClass() != o.getClass()) {
           return false;
      }
       Term term = (Term) o;
       return  this.getData().getIndex() == term.getData().getIndex() &&
               Objects.equals(this.tridatas, term.tridatas);
  }

step3:在表达式类中实现合并

public void merge() {
       ArrayList<Term> ts = new ArrayList<>();
       for (int i = 0; i < this.terms.size(); i++) {
           Term term = new Term(this.terms.get(i));   //new then set
           for (int j = i + 1; j < this.terms.size(); ) {
               if (term.equals(this.terms.get(j))) {
                   term.setData(new Data(term.getData().getConstant().add(this.terms.get(j).getData().getConstant()),term.getData().getIndex()));
                   this.terms.remove(j);       //no need j++ here
              } else {
                   j++;
              }
          }
           if (!term.getData().getConstant().equals(BigInteger.ZERO)) {
               ts.add(term);
          }
      }
       this.terms.clear();
       this.terms = ts;
  }

对优化前后的情况做一个对比

before:

after:

 

思考与总结

1.最重要的是整体架构和布局。敲键盘之前脑子里必须要有清晰的框架,不然开始写作业的时候肯定非常地卡顿,反而更浪费时间,而且后续进行测试的时候难以发现问题。

2.编写代码的时候一定要小心IDEA的自动补全,虽然十分方便快捷,但一不留神很可能补上错的内容,后续只能苦苦debug。

3.第一次接触到代码的迭代,特别认识到一开始进行初步设计的重要性,要让程序变得易于理解和可拓展,一开始就有很好的布局,后续的工作量绝对大大缩减了,可以将更多的精力放在优化上。

OO