BUAA OO Unit1

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

本单元三次作业内容为多项式求导。第一次作业为支持幂函数因子和常数因子的多项式求导;第二次作业增加了三角函数sin(x)、cos(x)因子以及表达式因子,表达式因子可以嵌套;第三次作业支持三角函数因子嵌套,并增加了对于输入数据格式判断进行的要求。总体而言,第一次作业较为简单,第二、三次作业的嵌套以及格式判断的难度较大。此外,对于第二、三次作业中的性能分,如何化简表达式也是较难处理的一部分。

一、程序结构分析

1.第一次作业

第一次作业的思路:直接对输入的表达式进行替换处理,采用正则表达式识别各项,并用ArrayList存放各个因子,在幂函数的因子类中设计求导方法,通过循环对整个ArrayList求导并合并同类项,最后输出求导后的表达式。

第一次作业共包含3个类:

MainClass:处理输入的表达式、创建表达式并求导、合并、输出

Poly:包含ArrayList属性的表达式类

Power:包含系数的幂函数因子类。

类图如下:

image

虽然第一次作业较为简单,但通过类图可以发现,虽然设立了3个不同的类,但并没有一个可扩展的层次结构,仍然没有从面向过程的编程思想转变为面向对象的编程思想,这也是第一次作业的不足之处。

经典的OO度量如下:

Class 代码长度 属性数 新增方法数 重写方法数
MainClass 87 13 1 0
Poly 87 1 7 0
Power 43 3 8 0
Total 217.0 17.0 16.0 0.0
Average 72.3 5.7 5.3 0.0

Class OCavg OCmax WMC
MainClass 12 12 12
Poly 3.25 10 26
Power 1.22 2 11
  • OCavg:平均操作复杂度
  • OCmax:最大操作复杂度
  • WMC: 加权方法复杂度

Method 分支语句数 控制语句数 代码长度
MainClass.main(String[]) 0 11 71
Poly.derivative() 0 1 5
Poly.getPoly() 0 0 3
Poly.merge() 0 2 9
Poly.mergeOp() 0 2 10
Poly.print() 0 9 34
Poly.sort() 0 0 8
Poly.sortFrist() 2 6 15
Power.Power(String,BigInteger,BigInteger) 0 0 5
Power.derivative() 0 1 8
Power.getCoef() 0 0 3
Power.getCoefop() 0 1 7
Power.getIndex() 0 0 3
Power.getOp() 0 0 3
Power.merge(Power) 0 0 3
Power.setCoef(BigInteger) 0 0 3
Power.setOp(String) 0 0 3
Total 2.0 33.0 193.0
Average 0.1 1.9 11.3

第一次作业复杂度尚可,在输出时为了将正项提前,使用了循环遍历,从而导致部分方法复杂度略高。

2.第二次作业

第二次作业的思路:第一种思路是利用数据结构,建立表达式树,设计Factor的父类,其他因子均继承Factor类,将Factor类的引用对象作为表达式树的结点,通过对树的遍历进行链式求导,这样体现了层次化设计的思想,更有利于因子的管理,体现了数据依从,但对于表达式的化简则不太方便。第二种思路则是利用容器,建立含有多个容器属性的项类,对于每个项再设计相应的容器属性,这样做的优势是利用容器可以很方便地合并同类项,从而对表达式进行化简,体现了计算依从,但对于表达式的求导则十分复杂。

考虑到正确性优先的原则,选择建立表达式树的方法,优先确保求导的正确性。

第二次作业共包含15个类,主要包含3个部分。

输入处理部分:ManageInput

表达式树部分:CreatTree、Tree、Node、Factor、Power、Const、Sin、Cos、Expression

求导化简部分:ManageExp、Deri

类图如下:

image

相比第一次作业,第二次作业增加了层次结构。ManageInput为处理输入表达式的类,将表达式格式化为运算符与因子交替出现的形式,方便后续建立表达式树;CreatTree、Tree、Node为建立表达式树的相关类;Power、Const、Sin、Cos、Expression是各个因子类,Factor是所有因子的父类;每个因子重写deri()方法,通过Deri类的方法统一调用;利用ManageExp类进行化简;此外还设计了Macro类来存储各个因子的正则表达式。

不足之处是Deri应设计为接口,而不是通过类的静态方法实现。此外,由于时间原因,化简部分仅实现了简单的去括号、去除0、1系数,没有实现合并同类项。

经典的OO度量如下:

Class 代码长度 属性数 新增方法数 重写方法数
Const 17 1 1 2
Cos 31 1 1 2
CreatTree 63 0 1 0
Deri 85 0 2 0
Expression 30 1 1 2
Factor 46 0 2 0
Macro 38 11 6 0
MainClass 21 0 1 0
ManageExp 40 0 1 0
ManageInput 54 0 3 0
Node 50 3 7 1
Oper 12 1 0 2
Power 36 1 2 2
Sin 29 1 1 2
Tree 29 1 3 0
Total 581.0 21.0 32.0 13.0
Average 38.7 1.4 2.1 0.87

Class OCavg OCmax WMC
Const 1 1 4
Cos 2.25 4 9
CreatTree 10 10 10
Deri 12.5 14 25
Expression 1.5 2 6
Factor 7 7 14
Macro 1 1 6
MainClass 2 2 2
ManageExp 9 9 9
ManageInput 3.33 6 10
Node 1.2 2 12
Oper 1 1 3
Power 2.4 5 12
Sin 2.25 4 9
Tree 1.75 4 7

Method 分支语句数 控制语句数 代码长度
Const.Const(BigInteger) 0 0 4
Const.clone() 0 0 3
Const.deri() 0 0 3
Const.toString() 0 0 3
Cos.Cos(BigInteger) 0 0 4
Cos.clone() 0 0 3
Cos.deri() 0 3 11
Cos.toString() 0 2 9
CreatTree.getRoot(String) 0 9 61
Deri.deri(Node) 0 10 34
Deri.deritoolong(String,String,String,String) 0 13 49
Expression.Expression(String) 0 0 3
Expression.clone() 0 0 3
Expression.deri() 0 1 12
Expression.toString() 0 1 9
Factor.clone(Factor) 0 6 22
Factor.deri(Factor) 0 6 22
Macro.getPatternConst() 0 0 3
Macro.getPatternCos() 0 0 3
Macro.getPatternExp() 0 0 3
Macro.getPatternItem() 0 0 3
Macro.getPatternPower() 0 0 3
Macro.getPatternSin() 0 0 3
MainClass.main(String[]) 0 1 19
ManageExp.deleteBracket(String) 1 9 38
ManageInput.manageBracket(String) 0 5 24
ManageInput.manageInput(String) 0 0 6
ManageInput.manageOrigin(String) 0 2 22
Node.Node(Factor) 0 0 5
Node.Node(Factor,Node,Node) 0 0 5
Node.add(Node,Node) 0 1 10
Node.getFactor() 0 0 3
Node.getLeftnode() 0 0 3
Node.getRightnode() 0 0 3
Node.setFactor(Factor) 0 0 3
Node.setLeftnode(Node) 0 0 3
Node.setRightnode(Node) 0 0 3
Node.toString() 0 1 7
Oper.Oper(String) 0 0 3
Oper.clone() 0 0 3
Oper.toString() 0 0 3
Power.Power(BigInteger) 0 0 3
Power.clone() 0 0 3
Power.deri() 0 4 13
Power.getIndex() 0 0 3
Power.toString() 0 3 11
Sin.Sin(BigInteger) 0 0 3
Sin.clone() 0 0 3
Sin.deri() 0 3 11
Sin.toString() 0 2 9
Tree.Tree() 0 0 3
Tree.add(Node,Node) 0 3 17
Tree.getRoot() 0 0 3
Tree.setRoot(Node) 0 0 3
Total 1.0 85 521
Average 0.02 1.57 9.65

第三次作业代码规模较为合理,但因为求导部分使用了递归,所以与求导相关的方法复杂度较高,在化简部分用了过多的if分支,导致部分方法长度过长。

3.第三次作业

第三次作业的思路:在第二次作业的基础上增量开发。设计工厂模式,生成不同的因子。修改Sin和Cos类的代码,增加因子属性,修改求导方法,实现三角函数的因子嵌套。利用递归下降语法分析增加格式判断,利用容器实现了合并同类项的化简,结合第二次作业的两种思路。

第二次作业共包含21个类,主要包含3个部分。

格式判断和输入处理部分:Parse、MyException、ManageInput

表达式树部分:CreatTree、Tree、Node、FactorFactory、Factor、Power、Const、Sin、Cos、Expression

求导化简部分:ManageExp、Deri、ManageMerge、Poly、Item

类图如下:

image

第三次作业与第二次作业的层次结构基本相似。Parse、MyException为格式判断相关的类,FactorFactory为因子工厂,ManageMerge、Poly、item为合并同类项相关的类。

不足之处是仍然没有利用好接口,以及设计的类过多,使得逻辑过于复杂,层次结构没有第二次作业清晰。

经典的OO度量如下:

Class 代码长度 属性数 新增方法数 重写方法数
Const 17 1 1 2
Cos 61 4 1 2
CreatTree 44 0 1 0
Deri 79 0 2 0
Expression 24 3 1 2
Factor 46 0 2 0
FactorFactory 46 0 1 0
Item 97 3 7 3
Macro 38 11 6 0
MainClass 31 0 1 0
ManageExp 47 0 2 0
ManageInput 58 0 4 0
ManageMerge 81 5 2 0
MyException 4 0 0 0
Node 50 3 7 1
Oper 12 1 0 2
Parse 200 8 10 0
Poly 22 1 1 1
Power 36 1 2 2
Sin 58 4 1 2
Tree 29 1 3 0
Total 1080.0 46.0 55.0 17.0
Average 51.43 2.19 2.62 0.81

Class OCavg OCmax WMC
Const 1 1 4
Cos 3.75 10 15
CreatTree 7 7 7
Deri 11.5 12 23
Expression 1 1 4
Factor 7 7 14
FactorFactory 9 9 9
Item 2.45 14 27
Macro 1 1 6
MainClass 2 2 2
ManageExp 5.5 9 11
ManageInput 2.75 6 11
ManageMerge 8 12 16
MyException 1 1 1
Node 1.2 2 12
Oper 1 1 3
Parse 4 15 44
Poly 2 4 6
Power 2.4 5 12
Sin 3.75 10 15
Tree 1.75 4 7

Method 分支语句数 控制语句数 代码长度
Const.Const(BigInteger) 0 0 4
Const.clone() 0 0 3
Const.deri() 0 0 3
Const.toString() 0 0 3
Cos.Cos(String,BigInteger) 0 0 7
Cos.clone() 0 0 3
Cos.deri() 0 9 36
Cos.toString() 0 2 9
CreatTree.getRoot(String) 0 6 42
Deri.deri(Node) 0 10 34
Deri.deritoolong(String,String,String,String) 0 11 43
Expression.Expression(String) 0 0 6
Expression.clone() 0 0 3
Expression.deri() 0 0 7
Expression.toString() 0 0 3
Factor.clone(Factor) 0 6 22
Factor.deri(Factor) 0 6 22
FactorFactory.generateFactor(String) 0 8 44
Item.Item() 0 0 5
Item.addCoef(BigInteger) 0 0 3
Item.equals(Object) 0 2 12
Item.getCoef() 0 0 3
Item.getOther() 0 0 3
Item.getPower() 0 0 3
Item.hashCode() 0 0 3
Item.mulCoef(BigInteger) 0 0 3
Item.mulOther(String) 0 1 7
Item.mulPower(BigInteger) 0 0 3
Item.toString() 0 13 44
Macro.getPatternConst() 0 0 3
Macro.getPatternCos() 0 0 3
Macro.getPatternExp() 0 0 3
Macro.getPatternItem() 0 0 3
Macro.getPatternPower() 0 0 3
Macro.getPatternSin() 0 0 3
MainClass.main(String[]) 0 2 29
ManageExp.deleteBracket(String) 1 9 38
ManageExp.manageExp(String) 0 1 7
ManageInput.manageBracket(String) 0 5 23
ManageInput.manageFactor(String) 0 0 5
ManageInput.manageInput(String) 0 0 6
ManageInput.manageOrigin(String) 0 2 22
ManageMerge.manageItem(String) 0 11 55
ManageMerge.merge(Poly,Item) 1 4 13
MyException.MyException() 0 0 2
Node.Node(Factor) 0 0 5
Node.Node(Factor,Node,Node) 0 0 5
Node.add(Node,Node) 0 1 10
Node.getFactor() 0 0 3
Node.getLeftnode() 0 0 3
Node.getRightnode() 0 0 3
Node.setFactor(Factor) 0 0 3
Node.setLeftnode(Node) 0 0 3
Node.setRightnode(Node) 0 0 3
Node.toString() 0 1 7
Oper.Oper(String) 0 0 3
Oper.clone() 0 0 3
Oper.toString() 0 0 3
Parse.Parse(String) 0 0 4
Parse.matchAddSub() 0 1 9
Parse.matchBlank() 0 1 12
Parse.matchExp() 0 3 18
Parse.matchFactor() 1 7 31
Parse.matchFactortoolong(Matcher,Matcher,Matcher,Matcher) 2 16 61
Parse.matchIndex() 0 3 17
Parse.matchItem() 0 2 13
Parse.matchLeftBracket() 0 1 8
Parse.matchMul() 0 1 8
Parse.matchRightBracket() 0 1 8
Poly.Poly() 0 0 3
Poly.getPoly() 0 0 3
Poly.toString() 0 3 13
Power.Power(BigInteger) 0 0 3
Power.clone() 0 0 3
Power.deri() 0 4 13
Power.getIndex() 0 0 3
Power.toString() 0 3 11
Sin.Sin(String,BigInteger) 0 0 6
Sin.clone() 0 0 3
Sin.deri() 0 9 34
Sin.toString() 0 2 9
Tree.Tree() 0 0 3
Tree.add(Node,Node) 0 3 17
Tree.getRoot() 0 0 3
Tree.setRoot(Node) 0 0 3
Total 5.0 170.0 974.0
Average 0.06 2.0 11.46

第三次作业中设计了过多的类,一方面是由于在化简部分没有与表达式树结合,而是对求导的结果重新化简,另一方面将可以在同一个类中实现的功能拆分成了多个类,在以后的学习中,还需要进一步理解高内聚、低耦合的面向对象的设计思想。

二、BUG分析

第一、三次作业中强测、互测均通过。

第二次作业中强测、互测各出现一处bug,原因都是在化简去括号时出现了错误。第一处是在对减法求导时,如果前一个数为0,不能直接去除括号,否则可能会出现乘号紧跟负号的错误情况。第二处是在判断表达式化简去括号时,如果表达式内第一个非空白字符为正负号且后面不是数字时,不能直接去除括号,否则可能会出现乘号紧跟正负号的错误情况。

修复了以上2个bug后,强测、互测均通过。

三、互测策略

在第二、三次作业互测中发现了其他同学的bug。

互测的策略是:自动测评机的黑箱测试 + 选择性阅读代码的手动测试

自动测评机:利用rstr包中的xeger函数根据正则表达式随机生成字符串,再利用sympy库中的diff、simplify等函数对随机生成的字符串求导,并与被测程序的结果进行化简比较。通常较为明显的求导、化简错误可通过自动测评发现,但难以覆盖StackOverFlow、TLE、格式判断的错误。

选择性阅读代码:由于时间的限制,不太可能仔细阅读每一份代码,因此,根据类的层次结构,选择类过多、层次结构过于复杂或类过少、面向过程编程的代码进行阅读,重点阅读输入数据处理部分、求导部分、以及化简部分,发现逻辑错误。对于复杂的递归,辅以手动输入可能导致爆栈、超时的数据进行测试。当然对于层次结构设计良好、面向对象编程的代码我们也应抱着学习的态度去阅读。

四、重构经历总结

在第二次作业时进行了重构。
image
image
通过比较第一、二次作业的类图可以看出,第一次作业在设计时欠缺对可扩展性的考虑,仅设计了第一次作业中出现的因子和相应的求导运算,并且均设计在了Power类的内部,此外,也没有考虑到数据存储的扩展,仅设计了简单的ArrayList,使得后续作业难以继续使用。

第二次作业重构后,设计了Factor因子父类以及deri求导的方法,增强了因子的可扩展性,新增因子时只需设计新增因子的类和求导方法即可。此外,采用表达式树的数据结构,将每个结点的数据域设计为Factor类,增强了数据存储的可扩展展性。在求导时,可按照树的结构进行链式求导,使得求导运算更加简单。第三次作业在第二次作业的基础上,仅修改了20行左右的代码就实现了三角函数支持因子嵌套,真正体会到了面向对象编程的魅力。

但此次重构也有不足之处,例如没有很好的使用接口,也没有考虑格式判断和化简的问题,使得在第三次作业中不得不额外地设计格式判断和化简模块,使得整个作业过于复杂,层次结构不够清晰,这也是以后在设计时应该考虑到的问题。

五、心得体会

第一单元是面向对象学习的开始,从面向过程编程到对面向对象、层次结构、设计模式和可扩展性有所了解并实际应用,以及对java的容器等有了更加深刻的认识,虽然还有很多不足,并且残留了面向过程的思想,但本单元对我的提升很大,从无到有地培养了面向对象的思想。希望在接下来的学习中能够再接再厉,进一步理解面向对象的构造与设计。

posted @ 2021-03-27 10:49  fangzhou0216  阅读(128)  评论(1)    收藏  举报
levels of contents