BUAA OO Unit1
面向对象构造与设计 第一单元总结
本单元三次作业内容为多项式求导。第一次作业为支持幂函数因子和常数因子的多项式求导;第二次作业增加了三角函数sin(x)、cos(x)因子以及表达式因子,表达式因子可以嵌套;第三次作业支持三角函数因子嵌套,并增加了对于输入数据格式判断进行的要求。总体而言,第一次作业较为简单,第二、三次作业的嵌套以及格式判断的难度较大。此外,对于第二、三次作业中的性能分,如何化简表达式也是较难处理的一部分。
一、程序结构分析
1.第一次作业
第一次作业的思路:直接对输入的表达式进行替换处理,采用正则表达式识别各项,并用ArrayList存放各个因子,在幂函数的因子类中设计求导方法,通过循环对整个ArrayList求导并合并同类项,最后输出求导后的表达式。
第一次作业共包含3个类:
MainClass:处理输入的表达式、创建表达式并求导、合并、输出
Poly:包含ArrayList属性的表达式类
Power:包含系数的幂函数因子类。
类图如下:

虽然第一次作业较为简单,但通过类图可以发现,虽然设立了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
类图如下:

相比第一次作业,第二次作业增加了层次结构。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
类图如下:

第三次作业与第二次作业的层次结构基本相似。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、格式判断的错误。
选择性阅读代码:由于时间的限制,不太可能仔细阅读每一份代码,因此,根据类的层次结构,选择类过多、层次结构过于复杂或类过少、面向过程编程的代码进行阅读,重点阅读输入数据处理部分、求导部分、以及化简部分,发现逻辑错误。对于复杂的递归,辅以手动输入可能导致爆栈、超时的数据进行测试。当然对于层次结构设计良好、面向对象编程的代码我们也应抱着学习的态度去阅读。
四、重构经历总结
在第二次作业时进行了重构。


通过比较第一、二次作业的类图可以看出,第一次作业在设计时欠缺对可扩展性的考虑,仅设计了第一次作业中出现的因子和相应的求导运算,并且均设计在了Power类的内部,此外,也没有考虑到数据存储的扩展,仅设计了简单的ArrayList,使得后续作业难以继续使用。
第二次作业重构后,设计了Factor因子父类以及deri求导的方法,增强了因子的可扩展性,新增因子时只需设计新增因子的类和求导方法即可。此外,采用表达式树的数据结构,将每个结点的数据域设计为Factor类,增强了数据存储的可扩展展性。在求导时,可按照树的结构进行链式求导,使得求导运算更加简单。第三次作业在第二次作业的基础上,仅修改了20行左右的代码就实现了三角函数支持因子嵌套,真正体会到了面向对象编程的魅力。
但此次重构也有不足之处,例如没有很好的使用接口,也没有考虑格式判断和化简的问题,使得在第三次作业中不得不额外地设计格式判断和化简模块,使得整个作业过于复杂,层次结构不够清晰,这也是以后在设计时应该考虑到的问题。
五、心得体会
第一单元是面向对象学习的开始,从面向过程编程到对面向对象、层次结构、设计模式和可扩展性有所了解并实际应用,以及对java的容器等有了更加深刻的认识,虽然还有很多不足,并且残留了面向过程的思想,但本单元对我的提升很大,从无到有地培养了面向对象的思想。希望在接下来的学习中能够再接再厉,进一步理解面向对象的构造与设计。

浙公网安备 33010602011771号