面向对象设计与构造-第一单元-总结博客

目录

1 第一次作业

1.1 架构设计
1.2 度量分析

2 第二次作业

2.1 架构设计
2.2 度量分析

3 第三次作业

3.1 架构设计
3.2 度量分析

4 测试与debug

5 心得体会

 

Homework1

1.1 架构设计

本次作业要求为解析表达式并化简,其中表达式由项通过加减法连接,项由因子(变量,常量,表达式)通过乘法连接。故本次作业分为两个模块,一是解析表达式,使用递归下降的办法构造表达式树;二是拆括号,合并同类项化简。

解析表达式

  1. 分析题意,可以很容易看出表达式-项-因子的层级关系,考虑到因子中表达式因子的设定,故采取递归下降的解析方案——设定读取与解析两个模块,并行操作。不断读入表达式,对于运算符/常数/变量判断后分别处理输入,输入模块将输入的字符串投入解析模块。解析模块自顶到下递归调用。

  2. 在解析过程中,对于表达式-项的存储,采用Arraylist;在项-因子的存储中,采用Hashmap<factor,index>,即为三种因子统一设置factor父类。

表达式化简

  1. 设置simplify接口,为表达式中的每个类重写expand函数。

  2. 为了方便合并同类项,统一化简成Hashmap<指数,系数>(特别注意指数才是唯一的,用作key)。具体实现则是,变量和常量是显然的;对于表达式因子和项,设置运算类,调用编写的加法与乘法函数,统一化简成Hashmap。利用hashmap的特性,将指数相同的进行合并。

1.2 度量分析

UML图


类复杂度

classOCavgOCmaxWMC
expr.Calculator 4.0 4.0 8.0
expr.Constant 1.0 1.0 4.0
expr.Expr 2.0 3.0 8.0
expr.Factor 1.0 1.0 2.0
expr.Term 2.0 4.0 10.0
expr.Variable 1.5 2.0 3.0
MainClass 5.0 7.0 15.0
Parser 2.62 5.0 21.0
TokenStream 2.67 7.0 16.0
Total     87.0
Average 2.42 3.78 9.67

类平均复杂度总体不高,其中Calculator和MainClass方法复杂度较高。主要因为我将本属于Expr类的打印方法放在了主函数中,其中又包含了计算方法,导致耦合度较高。

方法复杂度

MethodCogCev(G)iv(G)v(G)
expr.Calculator.mul(HashMap<Integer, BigInteger>, HashMap<Integer, BigInteger>) 7 1 4 4
expr.Calculator.plus(HashMap<Integer, BigInteger>, HashMap<Integer, BigInteger>) 5 1 4 4
expr.Constant.Constant(boolean) 0 1 1 1
expr.Constant.expand() 0 1 1 1
expr.Constant.setNum(BigInteger) 0 1 1 1
expr.Constant.toString() 0 1 1 1
expr.Expr.addTerm(Term) 0 1 1 1
expr.Expr.expand() 2 1 3 3
expr.Expr.Expr() 0 1 1 1
expr.Expr.toString() 3 1 3 3
expr.Factor.getPow() 0 1 1 1
expr.Factor.setPow(int) 0 1 1 1
expr.Term.addFactor(Factor) 0 1 1 1
expr.Term.expand() 4 2 4 4
expr.Term.flip() 0 1 1 1
expr.Term.Term() 0 1 1 1
expr.Term.toString() 3 1 3 3
expr.Variable.expand() 0 1 1 1
expr.Variable.toString() 2 2 2 2
MainClass.appendValue(int, StringBuilder, HashMap<Integer, BigInteger>) 13 1 7 7
MainClass.main(String[]) 0 1 1 1
MainClass.print(HashMap<Integer, BigInteger>) 12 5 8 9
Parser.parseExpr() 7 1 6 6
Parser.parseExprFactor() 0 1 1 1
Parser.parseFactor() 3 3 3 3
Parser.parseNum() 3 1 3 3
Parser.parsePow(Factor) 3 1 3 3
Parser.Parser(TokenStream) 0 1 1 1
Parser.parseTerm() 3 1 4 4
Parser.parseVariable() 0 1 1 1
TokenStream.blankHidden() 3 1 3 4
TokenStream.getNumber() 2 1 3 3
TokenStream.next() 8 3 4 7
TokenStream.powJudge() 3 3 2 3
TokenStream.symbol() 0 1 1 1
TokenStream.TokenStream(String) 0 1 1 1
Average 2.39 1.33 2.42 2.58

整体复杂度不高,主要还是集中在MainClass中的打印函数部分(涉及print和appendValue)。

 

Homework2

2.1 架构设计

概览

表达式->项:Hashmap<项,系数>

项->因子:Hashmap<因子,指数>

因子:常数;变量;三角函数因子;表达式因子(sum函数与自定义函数在解析阶段直接展开/替换成表达式因子)

sum函数的展开

在读入了循环的始末后,对sum函数内部的因子再次进行递归解析,没有针对于题目给定的几种因子的分类讨论,而是采取统一的递归下降解析因子的办法,返回一个解析后的因子类,这样的设计也满足了第三次作业中内部嵌套的需求。展开阶段,为了和自定义函数中的replace方法统一,这里将“i”作为形参,循环变量作为实参传入,返回替换后的因子类。再包装成单因子项,加入表达式的hashmap。于是,在初步解析后,sum因子不复存在,直接返回表达式因子。

自定义函数的替换

在预处理阶段获得自定义函数范式的过程中,使用hashmap<函数名,函数因子>并传入解析类。首先,读取解析获取自变量函数名,实参列表。利用hashmap,由函数名获取自定义函数范式,得到表达式因子类。再者,利用表达式的replace方法,传入形参和实参表,进行替换。在我的设计中,replace方法,同样采用递归下降的处理方式。好处是这样的设计使得替换的因子不受限制。

三角函数因子的添加与处理

单独为三角函数设置类,存储sin/cos函数名,内部因子和指数。解析中,对于内部因子再次采用递归下降处理因子的办法,即可解决内部因子嵌套的问题。在化简合并的过程中,我统一枚举hashmap<因子,指数>中的因子,利用重写的hashcode&equals方法,在抽象的因子层面比较异同,无需过多考虑sin/cos或者幂函数的形式差异。

2.2 度量分析

UML图

类复杂度

classOCavgOCmaxWMC
homework.Pair 1 1 3
homework.factors.Factor 1 1 5
homework.factors.Func 1 1 4
homework.factors.Function 1 1 5
homework.factors.Sum 1 1 6
homework.MainClass 2 2 2
homework.factors.Constant 1.272727273 3 14
homework.factors.Variable 2 4 16
homework.Calculator 3.333333333 5 10
homework.Parser 2.461538462 6 32
homework.factors.Expr 2.642857143 7 37
homework.factors.Term 2.1875 7 35
homework.TokenStream 3 8 21
homework.factors.Triangle 2.4 10 24
Total     214
Average 2.018867925 4.071428571 15.28571429

这次作业中,将上次放在MainClass中的打印方法放入每个类中,最后调用Expr的打印方法,故这次类的复杂度都比较低。

方法复杂度

MethodCogCev(G)iv(G)v(G)
homework.Pair.Pair(T, U) 0 1 1 1
homework.factors.Triangle.Triangle(String, Factor) 0 1 1 1
homework.factors.Triangle.expand() 0 1 1 1
homework.factors.Triangle.hashCode() 0 1 1 1
homework.factors.Triangle.replace(ArrayList, ArrayList) 0 1 1 1
homework.factors.Triangle.setFactor(Factor) 0 1 1 1
homework.factors.Variable.Variable(String) 0 1 1 1
homework.factors.Variable.expand() 0 1 1 1
homework.factors.Variable.hashCode() 0 1 1 1
homework.factors.Variable.optimize() 0 1 1 1
homework.Calculator.mulConst(Expr, BigInteger) 1 1 2 2
homework.MainClass.main(String[]) 1 1 2 2
homework.Parser.parseFunc(HashMap<String, Function>) 1 1 2 2
homework.Parser.parseSum() 1 1 2 2
homework.Parser.preFunc() 1 1 2 2
homework.factors.Constant.print(int) 1 2 1 2
homework.factors.Expr.expand() 1 1 2 2
homework.factors.Expr.optimize() 1 1 2 2
homework.factors.Expr.replace(ArrayList, ArrayList) 1 1 2 2
homework.factors.Expr.squeezeConstant() 1 1 2 2
homework.factors.Expr.toString() 1 1 2 2
homework.factors.Factor.deepClone() 1 1 2 2
homework.factors.Term.Term(HashMap<Factor, Integer>) 1 1 2 2
homework.factors.Term.optimize() 1 1 2 2
homework.factors.Term.replace(ArrayList, ArrayList) 1 1 2 2
homework.factors.Triangle.toString() 1 1 2 2
homework.factors.Variable.toString() 1 1 2 2
homework.Calculator.plus(Expr, Expr) 2 1 3 3
homework.TokenStream.getNumber() 2 1 3 3
homework.factors.Expr.addTerm(Term) 2 1 2 2
homework.factors.Expr.addTerm(Term, BigInteger) 2 1 2 2
homework.factors.Term.addFactor(Factor, int) 2 1 2 2
homework.factors.Term.equals(Object) 2 3 1 3
homework.Parser.parseNum() 3 1 3 3
homework.Parser.parsePow(Factor) 3 1 3 3
homework.Parser.parseTerm() 3 1 4 4
homework.TokenStream.blankHidden() 3 1 3 4
homework.TokenStream.getTriOrSum() 3 4 3 4
homework.TokenStream.isPow() 3 3 2 3
homework.factors.Constant.equals(Object) 3 3 2 4
homework.factors.Expr.equals(Object) 3 3 2 4
homework.factors.Expr.squeezeZero() 3 1 3 3
homework.factors.Triangle.equals(Object) 3 3 3 5
homework.factors.Triangle.print(int) 3 2 3 3
homework.factors.Variable.print(int) 3 2 3 3
homework.factors.Term.toString() 4 2 3 4
homework.factors.Variable.equals(Object) 4 3 3 5
homework.factors.Variable.replace(ArrayList, ArrayList) 5 4 3 4
homework.Parser.parseExpr() 7 1 6 6
homework.factors.Term.squeezeConstant() 7 1 5 5
homework.Calculator.mul(Expr, Expr) 9 1 5 5
homework.Parser.parseFactor() 9 6 12 12
homework.factors.Expr.appendValue(Term, StringBuilder, HashMap<Term, BigInteger>) 9 5 6 7
homework.TokenStream.next() 10 3 5 9
homework.factors.Term.expand() 11 1 7 7
homework.factors.Expr.print() 12 5 8 9
homework.factors.Triangle.optimize() 20 9 6 10
... ... ... ... ...
Average 1.622641509 1.424528302 1.962264151 2.20754717

方法复杂度总体不算高,但是三角优化部分和复杂度比较高,仅考虑了性能和正确性。parseFactor方法实际上复杂度并不高,只是由于有很多if条件语句,故值也较高。

 

Homework3

3.1 架构设计

由于第二次作业一定程度上的超前设计,整体架构基本没有改动。第二次作业中,因为没有进行三角优化,性能分比较拉垮,故在第三次作业中进行了sin(x)^2+cos(x)^2=1以及sin(-x)=-sin(x)的优化,具体实现如下:

第二个优化可以在去除常量优化中同时进行,当三角函数内的表达式只有一项,且该项的符号是负号时,就会根据诱导公式提取出来结合指数乘入表达式的系数。

而第一个优化相对比较复杂,而进行的也不彻底。首先遍历表达式中的所有项,判断其是否有某一指数大于等于2的三角函数因子,如果存在则构造它的对偶形式,如f(x)sin(g(x))^2->f(x)cos(g(x))^2。如果其对偶形式也存在的话,用f(x)*(1-cos(g(x))^2)代替原来的f(x)sin(g(x))^2,其他项保留不变,如此反复迭代直到表达式不再变化即为最终化简结果。

3.2 度量分析

UML图

类复杂度

classOCavgOCmaxWMC
homework.Pair 1 1 3
homework.factors.Factor 1 1 2
homework.factors.Function 1 1 5
homework.MainClass 2 2 2
homework.factors.Constant 1.2 3 12
homework.factors.Variable 1.714285714 4 12
homework.Calculator 3.333333333 5 10
homework.factors.Triangle 1.833333333 5 22
homework.Parser 2.538461538 6 33
homework.TokenStream 3 8 21
homework.factors.Term 2.916666667 10 35
homework.factors.Expr 3.2 11 48
Total     205
Average 2.277777778 4.75 17.08333333

整体架构还算清晰层次化,耦合现象不明显,类平均复杂度不算高。

方法复杂度

MethodCogCev(G)iv(G)v(G)
homework.Pair.Pair(T, U) 0 1 1 1
homework.Pair.getFirst() 0 1 1 1
homework.Pair.getSecond() 0 1 1 1
homework.Parser.Parser(TokenStream) 0 1 1 1
homework.Parser.Parser(TokenStream, HashMap<String, Function>) 0 1 1 1
homework.Parser.parseExprFactor() 0 1 1 1
homework.Parser.parseTri() 0 1 1 1
homework.Parser.parseVariable() 0 1 1 1
homework.factors.Function.getVars() 0 1 1 1
homework.factors.Triangle.setFactor(Expr) 0 1 1 1
homework.factors.Variable.Variable(String) 0 1 1 1
homework.factors.Variable.expand() 0 1 1 1
homework.factors.Variable.hashCode() 0 1 1 1
homework.factors.Variable.optimize() 0 1 1 1
homework.factors.Variable.toString() 0 1 1 1
homework.Calculator.mulConst(Expr, BigInteger) 1 1 2 2
homework.MainClass.main(String[]) 1 1 2 2
homework.Parser.parseSum() 1 1 2 2
homework.Parser.preFunc() 1 1 2 2
homework.factors.Expr.expand() 1 1 2 2
homework.factors.Expr.optimize() 1 1 2 2
homework.factors.Expr.replace(ArrayList, ArrayList) 1 1 2 2
homework.factors.Expr.squeezeConstant() 1 1 2 2
homework.factors.Factor.deepClone() 1 1 2 2
homework.factors.Term.optimize() 1 1 2 2
homework.factors.Term.replace(ArrayList, ArrayList) 1 1 2 2
homework.factors.Triangle.getNegType() 1 1 1 2
homework.Calculator.plus(Expr, Expr) 2 1 3 3
homework.TokenStream.getNumber() 2 1 3 3
homework.factors.Constant.equals(Object) 2 3 1 3
homework.factors.Expr.addTerm(Term) 2 1 2 2
homework.factors.Expr.addTerm(Term, BigInteger) 2 1 2 2
homework.factors.Expr.equals(Object) 2 3 1 3
homework.factors.Term.addFactor(Factor, int) 2 1 2 2
homework.factors.Term.equals(Object) 2 3 1 3
homework.Parser.parseNum() 3 1 3 3
homework.Parser.parsePow() 3 2 3 3
homework.Parser.parseTerm() 3 1 4 4
homework.TokenStream.blankHidden() 3 1 3 4
homework.TokenStream.getTriOrSum() 3 4 3 4
homework.TokenStream.isPow() 3 3 2 3
homework.factors.Expr.squeezeZero() 3 1 3 3
homework.factors.Triangle.equals(Object) 3 3 2 4
homework.factors.Variable.equals(Object) 3 3 2 4
homework.Parser.parseFunc(HashMap<String, Function>) 4 1 4 4
homework.factors.Term.toString() 4 2 3 4
homework.factors.Triangle.optimize() 5 4 2 4
homework.factors.Variable.replace(ArrayList, ArrayList) 5 4 3 4
homework.Parser.parseExpr() 7 1 6 6
homework.Calculator.mul(Expr, Expr) 9 1 5 5
homework.Parser.parseFactor() 9 6 12 12
homework.TokenStream.next() 10 3 5 9
homework.factors.Triangle.toString() 10 5 5 6
homework.factors.Term.expand() 11 1 7 7
homework.factors.Expr.appendValue(Term, StringBuilder, HashMap<Term, BigInteger>) 12 6 8 9
homework.factors.Expr.print() 13 1 7 7
homework.factors.Term.squeezeConstant() 27 7 9 10
homework.factors.Expr.triOptimize() 36 7 11 12
... ... ... ... ...
Average 2.411111111 1.566666667 2.188888889 2.477777778

可以明显看出,由于仅考虑了性能和正确性,三角优化和常量优化的复杂度比较高。parseFactor方法实际上复杂度并不高,只是由于有很多if条件语句,故值也较高。

 

测试与debug

中测前debug

笔者在中测debug过程中,三次均使用自动化评测的方式。第一次的数据生成器主要参考了王小鸽同学的帖子,对拍器部分使用sympy的expand等方法基于python完成。第二次由于引入了三角函数,求和函数与自定义函数,在构造sympy的输入表达式的时候,同时进行了自定义函数与求和函数的展开,之后便可以利用第一次的思路进行比对。第三次由于递归调用的存在,第二次的方法不太适用,直接采取了与伙伴对拍的方法,略微修改了自动评测机,便可以再次利用sympy比对两人的输出。三次测试准备还算比较充分,三次作业在强测和互测中,均未发现bug。

互测

互测阶段,笔者采用自动化评测+边界/特殊样例结合的方式。若在自动化评测中发现房友可能存在bug,则简化测试样例后,使用错误样例对房友代码debug。发现错因后,构造简单样例提交数据。第一次互测中,发起成功hack2次;第二次互测中,发起成功hack1次;第三次互测中,发起成功hack5次。

遇到的bugs

在互测阶段发现的伙伴的bug包含:优化过程中直接将“1*”替换成“ ”而忽略具体语意;嵌套括号后不符合语法规范问题;sum函数循环因子未使用biginteger而爆int出现exception.etc

我在第三次作业的debug过程中,发现由于在输出函数中toString()方法的错误调用,无意间破坏了对象的不可变性,导致了一个很离奇的bug。为了提高代码的稳健性,修改了指数的setPow()和getPow()函数,使得一经初始化的指数不再可更改。除此之外,我在debug过程中,还遇到了如在遍历hashmap时同时进行赋值,直接用=号对对象进行浅拷贝等bug。

 

心得体会

由于在pre中认识到自己的思维很多时候还是面向过程,所以完成第一单元时,着重关注面向对象的思维与实现,领悟并实现到诸如继承、多态等思想。再者,要写出这样的大型代码,架构是非常重要的。在前两次作业中,我都花了1到2天进行构思,然后才开始写,对自己的架构还是比较满意,期间也没有进行过重构。其次,在第一单元的学习中,我第一次学习写评测机,期间进行了大量的测试,并在提交前修好了几乎所有的bug,觉得这个过程很有意义也很有收获。最后,感谢所有和我一起讨论过架构,帮助过我的同学和学长。

posted on 2022-03-24 21:13  Doris_M  阅读(92)  评论(3编辑  收藏  举报