2019 面向对象第一单元总结(表达式求导)

2019 面向对象第一单元总结(表达式求导)

第一次作业

设计分析

  这是我第一次写的java程序,但是之前有过cpp的编程经历,对于类的理解与使用其实也还算过得去。

处理流程

  · 使用正则表达式识别错误空格格式,去除空格完成输入预处理

  · 构建Expression类储存输入表达式,同时在类中构建使用正则表达式识别项的方法

  · 构建Term类储存每一个项的内容,同时构建ArrayList termList储存每一个Expression中的项

  · 完善Exp 与 Term 类中的求导和输出方法,完成求导后的表达式输出

UML类图

复杂度分析

  可以看到由于operate与operate函数包含了从项中直接使用数据并通过循环遍历输出,导致其模块设计复杂度极高。之后会使用自底向上的统一化输出方法进行优化。

BUG修改以及优缺点分析

  设计的时候未考虑超出INT范围的情况没有使用BigInteger类型,导致强测半数测试点未通过,也是自己的不仔细和粗心导致了整个的失误,同时在代码中有一处输出的‘+’写成了‘-’。在整个设计中,Expression是主类,同时几乎承载着所有功能,其他类几乎只是起到一个储存数据的作用,究其原因是受到面向过程思想的影响较深。同时也有许多功能性方法能直接作为private写入到构造方法中,从而减少主方法的内容,提高主方法的可读性。

  这次的构造对于第二次作业来说其扩展性比较强,给出了整个运作的框架。


第二次作业

设计分析

  这次作业增加了三角函数,但是完全可以使用第一次作业的架构,因为第一次作业是使用<a,b>来表示a*x^b项,同时进行合并同类项操作。

  而本次作业则只需要进行简单的扩展,使用四元组<a,b,c,d>来表示a*x^b*sin(x)^c*cos(x)^d。

  同时使用了HashMap储存Term,同时重写hashCode与equals函数,使用TermKey这个装载三个函数幂的类作为Key来使用HashMap。

  同时采用复合求导方法将一个项的求导产生3个新项插入用于输出的ArrayList。

UML类图

复杂度分析

  可以看到由于采用了自底向上的输出操作,operate函数的模块设计复杂度降低的很明显,但是由于还是带着面向过程的思想,在Term的构造函数中内容过多,没有将其细分为方法拆出,导致代码主体过长,从而导致复杂度过高。

 

 

BUG修改及优缺点分析

  本次的强测没有出现bug,但是我认为本次的作业相比于上一次主要的问题其实是在代码结构的调整,与额外出现的优化操作,本次需要考虑的优化有三个,围绕sin^+cos^2=1进行,有两个减法的衍化形式,要考虑到优化过程中的能够合并的项的检测,以及使用贪心算法只有在减少项的时候才进行优化,并且考虑优化后产生的项可以进行再次优化而使用固定次数的循环优化。

  但是本次作业对于第三次而言几乎0扩展性,其对于输入的形式的严格要求所进行的许多简化操作,导致了其适配性与扩展性的低下。


第三次作业

设计分析

  本次作业区别在于加入了嵌套因子与表达式因子,这意味着需要出现递归分析的情形,也说明不能对其进行严格的分析,而是要将分析一步步进行,即分析出项的结构,然后再去分析因子的结构,需要递归调用的时候直接递归调用,保证其有确定的出口即可,在本题中递归出口则为幂函数以及常数项。

  故而本次的分析过程也是一步步下降的,先识别sin()cos()表达式因子的外层括号,将其替换为#,从而将正则表达式对括号内的特定格式内容识别改为对双#内的任意内容识别,对于其结构正确性,则在递归调用exp类时,达到常数项或者幂函数项出口过程中不出现格式报错则为格式正确。

  同时由于本次的优化及其难以进行,求导过程则直接使用字符串拼接解决,同时还能很好的解决自底向上的输出问题。

  并且在Expression内使用ArrayList储存Term,在Term内使用ArrayList储存Factor,同时为Factor创建5个子类,分别是常数,x,sin,cos,表达式因子。从而更好地管理。

UML类图

复杂度分析

  可以看到整体的复杂度都不是很高,但是由于某些函数里没有完全使用get方法来获取数据,并且没有拆分为两个方法,导致有些操作的复杂度还是很高的。并且由于使用了统一的接口并且时自底向上输出,可以看到即使是Expression的getOperate求导函数复杂度也是非常低的。

BUG修改及优缺点分析

  本次强测没有出现bug,但是本次的作业其实很好的让我理解了继承以及接口的使用好处。可以更好的管理代码之间的联络性,并且大幅度减少冗余代码和复用代码量,可以看到在使用了接口之后,求导工作其实变得简单起来,因为确定了输入与输出形式,只需要完成每一个类自己的求导方法的实体内容即可。

  同时本次作业将许多可以自动实施的操作直接作为方法在构造函数内使用,大大增加了使用类的简易度。

  本次作业封装,继承,多态,接口,重构都有所使用,体会到了其带来的工程化的好处,同时也明白一个好的前期规划以及对结构的剖析是多么重要。

  但是本次的优化做的十分糟糕,由于对应的情形十分多,只简单做了0与1的优化,以及重复括号的优化。其余的同类项合并以及更复杂的合并优化并没有进行。但是本次使用了一个小技巧,即将第一次实例化的Expression得到的求导内容直接作为输入实例化一个新的Expression,由此可以重复利用在Term构造阶段使用的0,1优化,算是一个时间换空间的小技巧吧。


BUG分析

  BUG测试也是一个工程中最重要的一个部分,在bug测试中主要有两个部分内容,分别为测试集构造以及bug位置查找。

  测试集构造使用了大量随机生成的表达式,但是没有很好的做好对拍器的工作,从而使bug测试无法很好的自动化的进行。同时也是用的手动构造来去逼近或者触及边界,同时在编写代码过程中就同步的对代码新增功能进行测试,避免在最终测试出现问题寻找问题来源的复杂性。

  bug位置查找,最简单的即在程序每一个部分之间加入对当前运行状态信息的输出,从而减少debug步入进行寻找对比状态差异。其次则可以在多个出现同等报错的地点加入当前位置信息输出,避免统一的报错信息之下难以查找的情况出现。之后则是学会使用异常处理,避免程序崩溃下无法抓取错误位置的情形。最后则是学会对大型输入报错能分析出最小的发生错误的子输入,或者能找到导致错误的最简单的输入形式,以本次作业而言则是从各个项入手,到选取其中连续的几个项测试,到用简单形式替代之后的测试。

  同时bug的测试中,同学们之间的讨论也是一个很重要的bug查找来源。

Applying Creational Pattern

  使用创造型模式来创建对象,虽然在本次作业中没能使用,但是其确实能契合本次的操作,只需要一个FactorFactory类,即可通过不同的输入条件来选择创建Fator类的哪一种子类,其实质上也是对接口,对继承,对封装的一个综合的应用。能站在问题总体输入输出的角度去看问题,而不是拘泥于过程方法为前驱去看待问题。

总结

  三次作业下来,可以看到再慢慢摆脱面向过程的思想的束缚,同时通过对方法的使用与理解的更加深化,以及对继承,多态,接口,重构的使用,能够让我在处理这种具有良好的层次化问题时,找到之间从属或者调用关系,以及被调用者之间的联系和共同之处,从而使代码结构之间有很强的逻辑性和耦合性,之后也要慢慢认真处理类成员和类方法的权限管理,学会使用get函数对类成员进行保护,养成良好的代码习惯,和良好的思维方式。

posted @ 2019-03-27 18:21  Sraey7  阅读(183)  评论(0编辑  收藏  举报