面向对象第一单元总结
面向对象第一单元总结
第一单元任务
第一单元的任务是实现对多项式的求导,分三次作业完成,难度循序渐进:
(1)第一阶段:简单一元多项式的求导,多项式仅包含x的幂次式。
(2)第二阶段:在第一阶段的基础上增加三角函数sin、cos,但是
sin和cos内仅允许出现x,多项式含三角函数幂次式。
(3)第三阶段:在第二阶段的基础上,增加三角函数嵌套,即sin、cos
内允许嵌套因子。
一、从过程到对象
由于之前基本上未接触过面向对象式编程,在第一次作业时,我只创建了一个主类,对输入的处理,读取字符串,以及最后的求导和化简,采用两条arraylist
第二次作业,增加了三角函数项sin和cos,我创建了factor类,其属性包含每个因子的系数,x、sin(x)、cos(x)的指数。相比第一次作业,有了类的初步概念。
第三次作业,增加了嵌套后,多项式也来越复杂,不能像第二次作业那样简单的处理因子,于是我创建了更多的类来管理各式各样的形式的因子,并使这些类实现同一个求导接口来集中管理这些类,通过多态实现对不同形式因子的求导。
相比第一次作业只有一个Main主类,到第二次作业有factor和Main两个主类,最后针对不同形式的因子创建不同的类,并通过接口统一管理,我渐渐有了面向对象的概念。
二、作业思路
(1)如何处理输入的字符串
由于第一次作业和第二次作业的多项式不支持嵌套,采用正则表达式便能很好的进行输入字符串的结构检查,以及提取项和因子。
但第三次作业由于表达式内三角函数的因子支持嵌套,采用正则表达式已经不能很好的对输入字符串进行结构检查以及提取项和因子。由于在大三上学习了编译原理这么课程,我于是采用了词法分析和语法分析对输入字符串进行处理:
- 首先定义词法单元:
- x
- 纯数字
- -+*^运算符
- sin
- cos
- (
- )
- 然后采用递归子程序法进行语法分析:
- Num = [-|+][0-9]*
- 因子 = Num | x [^ Num] | sin ( 因子) | cos ( 因子) | 因子)
- 项= [-|+]因子 (*因子)*
- 因子= [-|+]项 ([-|+]项)*
(2)提取表达式并进行求导
表达式维护一个arraylist,arraylist的每一个元素代表表达式的一项。让后每一项本身也维护一个arraylist,其每一个元素代表项的一个因子。每一个因子可以是sin类,可以是cos类,可以是幂次类,也可以是表达式类。
上述所有的类均实现同一个接口,以至于所有的这些类都可以通过顶层引用进行统一管理。接口声明求导方法,在对表达式求导的过程中,各个类实现不同的求导方法,通过多态对不同类进行求导。
二、程序分析
1、oo度量
- 重要符号意义说明:
- ev(G):基本复杂度,用来衡量程序非结构化程度的。
- Iv(G):模块设计复杂度,用来衡量模块判定结构,即模块和其他模块的调用关系。
- v(G):用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数。
- LOC: Line of Code
- NCLOC:Non-Commented Line Of Code
(1)第一次作业
Class | LOC | NCLOC |
---|---|---|
Derive | 187 | 147 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Derive.findindex(int,String) | 1.0 | 2.0 | 2.0 |
Derive.merge() | 1.0 | 4.0 | 4.0 |
Derive.main(String[]) | 2.0 | 12.0 | 12.0 |
Derive.findcoef(int,String) | 1.0 | 13.0 | 13.0 |
Total | 5.0 | 31.0 | 31.0 |
Average | 1.25 | 7.75 | 7.75 |
第一次作业可以看出,Derive类的main方法和findcoef方法iv(G)和v(G)两项的值都很大,说明对于方法的抽象不够,这个方法应该可以抽象成几个小部分,说明程序比较偏于面向过程。 |
(2)第二次作业
Class | LOC | NCLOC |
---|---|---|
Factor | 161 | 159 |
Main | 95 | 86 |
Total | 256 | 245 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Factor.Merge(Factor) | 1.0 | 1.0 | 1.0 |
Factor.Factor(String,String) | 1.0 | 1.0 | 1.0 |
Factor.getsin_index(String) | 1.0 | 2.0 | 2.0 |
Factor.getcos_index(String) | 1.0 | 2.0 | 2.0 |
Main.Legal(String) | 2.0 | 1.0 | 2.0 |
Factor.coef_zero() | 2.0 | 1.0 | 2.0 |
Factor.getx_index(String) | 1.0 | 2.0 | 2.0 |
Factor.equals(Factor) | 3.0 | 3.0 | 4.0 |
Main.HandleList(ArrayList |
1.0 | 6.0 | 6.0 |
Factor.Derive() | 1.0 | 8.0 | 8.0 |
Main.main(String[]) | 5.0 | 9.0 | 9.0 |
Factor.DealTerm(String,String) | 1.0 | 13.0 | 14.0 |
Total | 21.0 | 50.0 | 54.0 |
Average | 1.615 | 3.846 | 4.154 |
可以看出第二次作业,Factor类的DealTerm方法和Main类的main方法抽象度不高,可以进一步分解成多个小部分。 |
(3)第三次作业
Class | LOC | NCLOC |
---|---|---|
Add | 29 | 29 |
BasicNumber | 16 | 16 |
BasicPow | 36 | 36 |
Cos | 20 | 20 |
Element | 3 | 3 |
Expression | 202 | 201 |
Main | 77 | 69 |
Total | 507 | 498 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
BasicPow.diff() | 1.0 | 1.0 | 1.0 |
BasicNumber.diff() | 1.0 | 1.0 | 1.0 |
BasicPow.main(String[]) | 1.0 | 1.0 | 1.0 |
Multiply.main(String[]) | 1.0 | 1.0 | 1.0 |
Symbol.Symbol(int) | 1.0 | 1.0 | 1.0 |
Sin.Sin(Element) | 1.0 | 1.0 | 1.0 |
Expression.Expression(String) | 1.0 | 1.0 | 1.0 |
Add.Add() | 1.0 | 1.0 | 1.0 |
Cos.toString() | 1.0 | 1.0 | 1.0 |
Expression.showDifflist() | 1.0 | 1.0 | 1.0 |
BasicNumber.toString() | 1.0 | 1.0 | 1.0 |
Multiply.AddList(Element) | 1.0 | 1.0 | 1.0 |
Sin.diff() | 1.0 | 1.0 | 1.0 |
BasicNumber.BasicNumber(String) | 1.0 | 1.0 | 1.0 |
Expression.generateDifflist() | 1.0 | 1.0 | 1.0 |
Cos.diff() | 1.0 | 1.0 | 1.0 |
Multiply.Multiply() | 1.0 | 1.0 | 1.0 |
Expression.generateTermlist() | 1.0 | 1.0 | 1.0 |
Cos.Cos(Element) | 1.0 | 1.0 | 1.0 |
Sin.toString() | 1.0 | 1.0 | 1.0 |
Add.AddList(Element) | 1.0 | 1.0 | 1.0 |
Add.diff() | 1.0 | 2.0 | 2.0 |
Symbol.equals(Symbol) | 2.0 | 1.0 | 2.0 |
Main.Legal(String) | 2.0 | 1.0 | 2.0 |
BasicPow.BasicPow(String) | 1.0 | 2.0 | 2.0 |
Pow.Pow(Element,String) | 1.0 | 2.0 | 2.0 |
Pow.diff() | 1.0 | 2.0 | 2.0 |
Pow.toString() | 1.0 | 3.0 | 3.0 |
BasicPow.toString() | 1.0 | 2.0 | 3.0 |
Add.toString() | 1.0 | 3.0 | 3.0 |
Multiply.diff() | 1.0 | 4.0 | 4.0 |
Multiply.toString() | 1.0 | 5.0 | 5.0 |
Expression.statement() | 1.0 | 3.0 | 5.0 |
Main.BlankTest(String) | 6.0 | 1.0 | 6.0 |
Expression.analyseSinCos() | 4.0 | 5.0 | 6.0 |
Main.main(String[]) | 4.0 | 6.0 | 7.0 |
Expression.analyseNumber() | 1.0 | 5.0 | 7.0 |
Expression.getsym() | 1.0 | 10.0 | 11.0 |
Expression.term() | 1.0 | 7.0 | 12.0 |
Expression.factor() | 7.0 | 8.0 | 15.0 |
Total | 59.0 | 93.0 | 120.0 |
Average | 1.475 | 2.325 | 3.0 |
2、类图
(1)第一次作业
(2)第二次作业
(3)第三次作业
三、程序优缺点
- 优点:
- 采用递归下降子程序法进行表达式的提取分析
- 将不同类型函数抽象为不同的类,所有的函数类实现相同的求导接口,通过顶层引用同一管理不同的函数类,并通过多态实现对不同类的求导
- 在第三次作业相对于前两次作业,类以及类方法的抽象程度更高,逐步走出了过程化编程
- 缺点:
- 第一次作业未考虑大数输入,未采用BigInteger,导致大数测试点全崩
- 第二次作业未进行cosx2+sin2=1等优化,导致优化分偏低
- 第一次和第二次作业所创建的类太少,主要还是过程化编程,代码抽象程度较低,未能很好实现高内聚,低耦合的编程思想
- 第三次作业表达式类过于冗余,Main主类的抽象程度可以进一步优化
四、程序Bug分析
- 第一次作业
- 自己私下测试时,未进行长表达式的处理,比如500个x连加,采用默认的正则表达式匹配会出现爆栈现象
- 未使用BigInteger进行系数和指数的存储,例如12345678987654321*x此类的大系数、大指数输入会导致程序崩溃
- 第二次作业
- 未考虑scanner在处理输入时如果什么都不输入(ctrl+D),会导致程序崩溃。采用try-catch能有效解决该异常现象
- 进行空白字符分析时,未考虑\s的真实含义,将垂直制表符等空白字符当作合法输入
- 第三次作业
- 未考虑在进行带表达式因子求导输出时,未加()的bug。例如x*(x+1),我第一次输出导数为1*x+1+x*1+0。在项带表达式因子时,应该在合理的位置加上括号,即1*(x+1)+x*(1+0)
- 未考虑输出形式上的规定,即不应该输出sin(2*x),而应该输出sin((2*x))
五、单元个人总结
经过三次多项式求导的训练,逐渐从过程化思想过度到面向对象思想,学习了继承,接口等对象化编程的一些方法,但是三次的练习还远远不够,希望能进一步理解面向对象编程的思想,使类的抽象程度更高,写出更加美观的java代码。