OO第一次博客作业
引言:
面向对象程序设计课程第一阶段的三次作业已经结束。这个阶段的三次作业均是关于多项式求导,难度逐次递增,项目需要实现的功能逐渐丰富。本文按各次作业划分版块,每个版块主要以代码设计,程序度量分析和bug分析三个方面来展开此篇博客的内容,其中bug分析包括对自己程序的分析以及在互测中发现的问题。最后谈点自己在完成这阶段作业时的收获。
第一次作业:
作业简介:
输入的多项式含有幂函数变量项、常数项;求导涉及带系数幂函数求导法则。
代码设计:
下图是第一次作业的设计思路:
- 构建正则表达式:
先构造项的正则表达式,再由项的正则表达式构建多项式的正则表达式。实现了对整行输入的匹配。 - 判断合法后处理输入方便提取项:
在构思解题思路时,想通过加号作为分隔符分隔每项。最后形成的解决方案如下:- 第一步:用单个 + 或 - 符号替换诸如 -- , +- 等这样重复的运算符号;
- 第二步:用 +- 符号替换 - 符号;再用 ^ 替换 ^+,从而实现可以用加号分隔每项,并且避免了幂函数正指数的 + 对分割产生的影响。分隔出的字符串存下它的系数和指数。
- 求导:
运用指导书给定的求导法则,对每项进行求导,更新每项的系数和指数。 - 输出前的处理:
在项这个类里重写toString(),对系数为0,1;指数为0,1的特殊进行处理;
在最后输出环节调用项的toString()来构建最后输出的多项式。特别的,如果所有项中含系数为正数的项,那么最后输出的第一项的系数一定为正数。
本次作业的类图
度量分析:
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
work.ExtractItems.extract(String) | 3.0 | 12.0 | 12.0 |
work.Item.getCoe() | 1.0 | 1.0 | 1.0 |
work.Item.getIndex() | 1.0 | 1.0 | 1.0 |
work.Item.Item() | 1.0 | 1.0 | 1.0 |
work.Item.setCoe(BigInteger) | 1.0 | 1.0 | 1.0 |
work.Item.setIndex(BigInteger) | 1.0 | 1.0 | 1.0 |
work.Item.toString() | 10.0 | 10.0 | 10.0 |
work.ProcessInput.removeDuplicateSymbol(String) | 1.0 | 1.0 | 1.0 |
work.ProcessInput.removeSpace(String) | 1.0 | 1.0 | 1.0 |
work.ProcessInput.segmentItem(String) | 1.0 | 1.0 | 1.0 |
work.WorkBegin.checkIllegalNum(String) | 2.0 | 1.0 | 2.0 |
work.WorkBegin.isCorrect(String) | 1.0 | 1.0 | 1.0 |
work.WorkBegin.main(String[]) | 1.0 | 3.0 | 3.0 |
work.WorkBegin.printList(ArrayList |
6.0 | 10.0 | 11.0 |
Total | 31.0 | 45.0 | 47.0 |
Average | 2.2142857142857144 | 3.2142857142857144 | 3.357142857142857 |
class | OCavg | WMC |
---|---|---|
work.ExtractItems | 10.0 | 10.0 |
work.Item | 2.5 | 15.0 |
work.ProcessInput | 1.0 | 3.0 |
work.WorkBegin | 4.25 | 17.0 |
Total | 45.0 | |
Average | 3.2142857142857144 | 11.25 |
复杂度过高原因分析:
因为自己的思路是通过 + 分隔每项,实现这点需要对合法输入进行大量处理,而处理函数单独开在一个字符串处理类中,导致提取项方法间的耦合度过高。
WorkBegin类中的main()函数在设计时,被当作整个流程的调度者,其复杂度也过高。
对于字符串输出,此次作业未专门新建类,直接塞在WorkBegin类中,回想起来,当时的做法非常不合理。
Bug分析:
此次自己程序出现的bug:
这次在公测中未被测出bug。在互测环节,因为系数互为相反数,指数相同项的存在,处理字符串时,chatAt()方法对一个空的字符串进行了访问,因此被hack了一个同质bug。
互测时发现的问题:
可能是因为接触java时间不长的原因,很多人都没有进行异常处理,导致本地用控制台结束符可以使程序奔溃(当然第一次提交不上这个样例)。其他方面,有部分人用 \s 处理空白字符,而导致输入出现\f等一些不可见字符时会输出错误。
第二次作业
作业简介:
相较于第一次作业要求,项的种类增加了三角函数;求导法则增加了乘法求导法则。
代码设计:
此次设计代码时,未对一些在构思思路时已经意识到的问题想出解决方案,譬如因子求导结果如何传给项、项的求导结果如何传给多项式,直接写代码,最后导致在周一出现了几乎算重构代码似的局面。
最后,对于求导结果传递的问题,想出的解决办法是:因子求导后传给项对象一个新实例化的项,项对象维护一个项的list,最后将这个list传给多项式。
此次作业,对于因子使用了继承。三角函数因子,幂函数因子都继承一个抽象类因子。通过继承和多态增加了代码的易读性和可维护性。
而因sin函数和cos函数求导和输出的行为差别较大,将其分开定义成类。最后形成多项式-项-因子的层级式结构,高层对象都有底层对象的list成员变量。
本次作业的类图:
度量分析:
class | OCavg | WMC |
---|---|---|
work.CosFactor | 2.6666666666666665 | 8.0 |
work.Factor | 1.0 | 3.0 |
work.FormatErrorException | 1.0 | 1.0 |
work.Item | 3.0 | 27.0 |
work.Poly | 5.857142857142857 | 41.0 |
work.PowerFactor | 2.6666666666666665 | 8.0 |
work.SinFactor | 2.6666666666666665 | 8.0 |
work.WorkBegin | 1.0 | 1.0 |
Total | 97.0 | |
Average | 3.2333333333333334 | 12.125 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
work.CosFactor.CosFactor(BigInteger) | 1.0 | 1.0 | 1.0 |
work.CosFactor.derivate() | 1.0 | 4.0 | 4.0 |
work.CosFactor.toString() | 3.0 | 3.0 | 3.0 |
work.Factor.Factor(BigInteger) | 1.0 | 1.0 | 1.0 |
work.Factor.getIndex() | 1.0 | 1.0 | 1.0 |
work.Factor.setIndex(BigInteger) | 1.0 | 1.0 | 1.0 |
work.FormatErrorException.FormatErrorException() | 1.0 | 1.0 | 1.0 |
work.Item.Derivate() | 4.0 | 3.0 | 4.0 |
work.Item.equals(Item) | 3.0 | 2.0 | 3.0 |
work.Item.extractItem(String) | 1.0 | 9.0 | 9.0 |
work.Item.getCoe() | 1.0 | 1.0 | 1.0 |
work.Item.getFa(int) | 1.0 | 1.0 | 1.0 |
work.Item.getFListSize() | 1.0 | 1.0 | 1.0 |
work.Item.Item(String) | 1.0 | 1.0 | 1.0 |
work.Item.setCoe(BigInteger) | 1.0 | 1.0 | 1.0 |
work.Item.toString() | 2.0 | 5.0 | 6.0 |
work.Poly.checkIllegalNum() | 3.0 | 1.0 | 3.0 |
work.Poly.creatItemList() | 3.0 | 3.0 | 5.0 |
work.Poly.derivateAndMerge() | 7.0 | 9.0 | 10.0 |
复杂度过高原因分析:
本次作业检查合法性采用了先进行静态分析,再递归下降检查合法性的方法,使的检查合法性函数的复杂度下降了一些。
由于要对输出的长度进行优化,在多项式类中定义了很多合并,分解的化简方法,导致多项式类的复杂度过高。
Bug分析:
自己的问题:
此次因对指导书中三角函数格式合法性的理解有误,导致强测和互测的场面十分惨烈。
互测时的感想:
此次互测中,很多人做了对输入异常的处理,检查字符串合法性的方法也变得灵活了很多。但是由于在构建正则表达式时考虑得不太充分,对于有些简单的错误输入,一些同学的代码不能判断其格式错误。
第三次作业:
作业简介:
引入了多项式因子、嵌套因子;求导法则增加了复合函数的求导法则。
代码设计:
设计模式:工厂模式
阅读过课程组分享的第二次作业优秀代码,见识到了一些可应用在此类作业的设计模式。在第三次作业,我运用了工厂模式,在项实例化因子对象时,直接将因子的信息传递给FactorFactory,工厂自动返回具体的对象。
解决嵌套而导致运行超时的问题
此次作业的第一版本的代码设计得非常糟糕,处理嵌套层数多的表达式因子时,譬如外层有29层括号的x时,程序运行会超时。
为了解决嵌套而导致运行超时的问题,我这次作业的解决办法是在多项式实例化项之前,将可以去除的括号全部去除。
本次作业的类图:
度量分析:
class | OCavg | WMC |
---|---|---|
error.WrongFormatException | 1.0 | 1.0 |
error.WrongIndexException | 1.0 | 1.0 |
item.factor.CosFactor | 4.25 | 17.0 |
item.factor.ExpFactor | 2.8 | 14.0 |
item.factor.FactFactory | 6.0 | 6.0 |
item.factor.Factor | 1.0 | 3.0 |
item.factor.PowerFactor | 3.0 | 9.0 |
item.factor.SignNum | 1.0 | 3.0 |
item.factor.SinFactor | 4.25 | 17.0 |
item.poly.Poly | 4.375 | 35.0 |
item.term.Term | 4.8 | 48.0 |
match.CheckExp | 4.2 | 21.0 |
match.CheckIllegalIndex | 3.0 | 3.0 |
match.CheckSpace | 4.0 | 4.0 |
process.ProcessString | 1.0 | 1.0 |
WorkBegin | 1.0 | 1.0 |
Total | 184.0 | |
Average | 3.5384615384615383 | 11.5 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
WorkBegin.main(String[]) | 1.0 | 2.0 | 2.0 |
process.ProcessString.deleteOperator(String) | 1.0 | 1.0 | 1.0 |
match.CheckSpace.check(String) | 4.0 | 1.0 | 4.0 |
match.CheckIllegalIndex.check(String) | 3.0 | 2.0 | 3.0 |
match.CheckExp.checkPurePoly(String) | 2.0 | 1.0 | 2.0 |
match.CheckExp.checkPureFactor(String) | 2.0 | 1.0 | 2.0 |
match.CheckExp.checkNest(String,int) | 8.0 | 17.0 | 19.0 |
match.CheckExp.CheckExp(String) | 1.0 | 1.0 | 1.0 |
match.CheckExp.checkBegin() | 2.0 | 1.0 | 2.0 |
item.term.Term.toString() | 5.0 | 7.0 | 7.0 |
item.term.Term.Term(String) | 1.0 | 1.0 | 1.0 |
item.term.Term.Term() | 1.0 | 1.0 | 1.0 |
item.term.Term.setCoe(BigInteger) | 1.0 | 1.0 | 1.0 |
item.term.Term.merge(Factor) | 8.0 | 9.0 | 9.0 |
item.term.Term.getFactorList() | 1.0 | 1.0 | 1.0 |
item.term.Term.getCoe() | 1.0 | 1.0 | 1.0 |
item.term.Term.extractItem(String) | 1.0 | 13.0 | 14.0 |
item.term.Term.equals(Term) | 7.0 | 11.0 | 11.0 |
item.term.Term.derivate() | 4.0 | 3.0 | 4.0 |
item.poly.Poly.toString() | 3.0 | 7.0 | 8.0 |
item.poly.Poly.Poly(String) | 1.0 | 1.0 | 1.0 |
item.poly.Poly.Poly(ArrayList |
1.0 | 1.0 | 1.0 |
item.poly.Poly.merge() | 4.0 | 4.0 | 5.0 |
item.poly.Poly.getList() | 1.0 | 1.0 | 1.0 |
item.poly.Poly.derivate() | 1.0 | 4.0 | 4.0 |
item.poly.Poly.createTermList() | 1.0 | 12.0 | 13.0 |
item.factor.SinFactor.toString() | 3.0 | 3.0 | 3.0 |
item.factor.SinFactor.SinFactor(String) | 1.0 | 11.0 | 12.0 |
item.factor.SinFactor.getFa() | 1.0 | 1.0 | 1.0 |
item.factor.SinFactor.derivate() | 1.0 | 4.0 | 4.0 |
item.factor.SignNum.toString() | 1.0 | 1.0 | 1.0 |
item.factor.SignNum.SignNum(String) | 1.0 | 1.0 | 1.0 |
item.factor.SignNum.derivate() | 1.0 | 1.0 | 1.0 |
item.factor.PowerFactor.toString() | 3.0 | 3.0 | 3.0 |
item.factor.PowerFactor.PowerFactor(String) | 1.0 | 2.0 | 2.0 |
item.factor.PowerFactor.derivate() | 1.0 | 4.0 | 4.0 |
item.factor.Factor.setIndex(BigInteger) | 1.0 | 1.0 | 1.0 |
item.factor.Factor.getIndex() | 1.0 | 1.0 | 1.0 |
item.factor.Factor.Factor() | 1.0 | 1.0 | 1.0 |
item.factor.FactFactory.product(String) | 6.0 | 2.0 | 6.0 |
item.factor.ExpFactor.toString() | 1.0 | 2.0 | 2.0 |
item.factor.ExpFactor.getPoly() | 1.0 | 1.0 | 1.0 |
item.factor.ExpFactor.ExpFactor(String) | 1.0 | 8.0 | 9.0 |
item.factor.ExpFactor.derivate() | 1.0 | 3.0 | 3.0 |
item.factor.ExpFactor.checkIndex(String) | 2.0 | 1.0 | 2.0 |
item.factor.CosFactor.toString() | 3.0 | 3.0 | 3.0 |
item.factor.CosFactor.getFa() | 1.0 | 1.0 | 1.0 |
item.factor.CosFactor.derivate() | 1.0 | 4.0 | 4.0 |
item.factor.CosFactor.CosFactor(String) | 1.0 | 11.0 | 12.0 |
error.WrongIndexException.WrongIndexException() | 1.0 | 1.0 | 1.0 |
error.WrongFormatException.WrongFormatException() | 1.0 | 1.0 | 1.0 |
Total | 103.0 | 177.0 | 199.0 |
Average | 2.0961538461538463 | 3.5384615384615383 | 4.038461538461538 |
复杂度过高原因分析:
因为此次三角函数因子包含了嵌套因子,在处理这个问题时,此次作业直接为三角函数因子设置了一个表达式成员变量。在进行复合函数求导时,三角函数求导方法就要去调用表达式的求导方法。在这类嵌套调用的过程中会消耗很多的内存资源。
对于整个项目的结构,在与一些同学交流后,发现这次作业的架构大家设计的都差不多,只是在实现的思路有差别。
Bug分析:
自己的bug
虽然对表达式可去除的括号进行了删除,但是对于诸如(x(x(x*(x))))此类的数据,此次作业去除括号降低嵌套深度的方法就不再奏效,在互测中也被屋里的人用此类数据造成了TLE类的错误。
互测时发现的问题:
此次互测不允许提交正确结果为WF的数据,主要提交的样例是针对互测屋内中实现复杂度较高、不对嵌套因子进行处理的代码而构造的。
一点收获
这三次作业中,发现自己设计程序时面向过程的思维过于顽固,在互测期间,我给同学讲述自己的实现时,同学轻易地指出了我哪些地方的写法可拓展性差,后来通过阅读课程组分享的优秀代码,我才开始系统学习例如工厂模式、单例模式等设计模式。
此外,在写完三次OO课下作业后,自己对完成一份优良代码所需要的流程有了自己的认识,加深了自己面向对象的思维,学会去构成类,再将属性、功能抽象出来。