oo第一单元总结
话不多说,上来先贴出我第三次作业的类图和度量:
这个图看不太清楚,这里只是用这个图来说明我的大致思路是什么样的。不难看出,即使到了第三次作业,我的思路还是可以用两个字来概括:很乱。结合这张几乎完全一点儿都看不清楚的图,我来讲一下我第三次作业的程序结构。
在分部分介绍我的各个类之前,我得先介绍一下我程序的整体框架,或者说总体思路。最开始的Main类向Polynomial类中传入Scanner类读入的字符串生成一个Polynomial的对象,Polynomial类将这个字符串切分成一个个的项,并根据这项的字符串生成接下来将要介绍的项类(Element类)的对象。在Elemen对象里进一步拆分成一个个的因子,根据每个因子不同的格式进一步将因子分为普通因子类和表达式因子类(这里dalao似乎还把普通因子分为了三角函数,幂函数和常数因子)。在这两种因子中,表达式因子是一种比较特殊的因子,因为它是由以"(表达式)"这样的形式组成的也就是说,因子之中包含了表达式。这之中就存在这样一种循环,表达式最基本的组成结构是因子,因子中又可以包含表达式,而这一表达式最基本的组成结构又是因子,如此循环。鉴于表达式的这种特性,我决定用递归的思想来构建我的表达式,至于递归的终点,就是一个不包含表达式因子的表达式。接下来围绕我程序的类图来讲一下不同类的内部情况。
首先,最下面的是我整个程序的主类,其中包括了入口方法public static void main(String[] args){}。
在Main类中除了main入口方法,还有三个check方法,是用来判断输入是否合法的。我听过其他同学判断输入是否合法的方式,有很多都是在各自的类中判断是否合法,比如把表达式切分成一个个项之后,在一个单独项的对象里对这个项进行合法性判断,项的合法性判断方法里会根据因子的合法性判断结果,结合因子之间的连接符号判断是否是合法输入。大概是这个意思。然而我不是这么写的,我是一次性判断出输入是否合法的,这个判断方法就是我写的checked方法,在这个方法中我运用了栈的基本思想:先进先出,后进后出(但并没有实际用到栈的操作),以此为原则,我对"("和")"进行了匹配(当然其中还包含了对表达式因子和sin|cos()的区别处理),在对其中内容的处理过程中,我用了表达式合法性检测方法check,和因子合法性检测方法check1(这里的check和check1基本都是针对第二次作业的,只作了一些必要的微调)。
combine,simplify和output是作业二的历史遗留问题忘了删了,请忽略(因为combine和simplify原本是我用来合并同类项,化简用的,而作业三我没有合并化简表达式,至于output是将我定义的Factor类转化为字符串输出,我将他转移到对应的类里面去了)。
接下来,在主类上面的,是表达式类:
表达式类的基本功能,是根据输入到构造方法的字符串参数,生成这个字符串表达式的项(即Arraylist<Element> elemrnts,许多Element类对象构成的Arraylist),生成表达式的导数(derivedgunction)以及输入的表达式参数本身。至于求导过程,表达式类中的求导方法需要项类的求导结果返回到表达式类,也就是说,一个表达式对象中elements所有的项类对象都已经求导完毕了,才能根据这些项类对象的求导结果进行表达式求导。
在类表中位于表达式类上方的是项类(Element类):
项类可以说是我写的最失败的一个类了,首先包含了许多之前作业用到,但在作业三中应该删除却没删的方法。其次,有很多变量其实没必要把它设置为成员变量,只要作为某一个方法中的过程变量就可以了,但是由于设计时思路不清晰,导致增添了了一些无用的成员变量,使得整个类显得冗长,条理不清晰。(由于思路过于混乱,我就不一一介绍我类中成员变量以及方法的作用了)
整理一下思路,我的Element类其实就实现了两大主要功能(其实和表达式类功能是相似的):求导和把项切分成因子。
前面在表达式的求导中已经提到过,项的求导是表达式求导的必要条件。与之类似,项的求导必须建立在因子求导的基础上。具体来讲,一个项类对象的totalderivation(即项的导数字符串),是根据该对象的(Arraylist<Factor>)factors中的每个普通因子的求导结果,以及(Arraylist<Polynomialfactor>) polynomialfactors中的每一个表达式因子的求导结果,结合求导法则得到的。PS:项类的(Arraylist<Factor>)factors存储的是组成该项类对象的所有普通因子,(Arraylist<Polynomialfactor>) polynomialfactors存储的是表达式因子。
最后是位于类表最上方的两个类,Factor和Polynomialfactor类,两个因子类:
因子类分为两种,普通因子类和表达式因子类。和表达式类、项类不同。因子是组成表达式最基本的单位了,所以它不需要在拆分,它所要做的只有一件事儿,那就是求导。普通类因子求导不难,不必多说直接求导。表达式因子求导就存在一个前面提到的递归求导的问题,因为表达式因子的求导需要以因子中的表达式的求导结果为基础,而正如本文前面提到的,表达式因子求导需要项的求导结果,项的求导结果又需要因子的求导结果,而因子中的表达式因子求导有又需要表达式求导结果...如此就构成了一个递归的模式,递归的终点就是普通因子,因为不管表达式因子嵌套几层,它最内层的表达式因子是不再包含表达式因子的(不然也不叫最内层的表达式因子了),也就是说这个表达式因子只由普通因子构成,那么这个表达式因子的求导结果就可以求出来了。再根据递归的原理,就可以求出整个表达式的导数。
接下来强行插入我第三次作业的度量
可以看出,程序中许多方法行数太长,一般这样的方法圈复杂度也很高,圈复杂度大,说明程序代码质量低,且难以测试和维护。在三次作业改写我代码的过程中,我也发现了这一问题:把太多事情放在一个方法里处理,导致在需要拆分这个方法时,由于该方法与其他代码段的联系,不好把这些功能拆分开,只能重构。于是,我总结了一条不知道是否正确的结论:能分就分,每个方法坚决只干一件事。
debug:
我在互测阶段每找到别人多少bug,自己倒是被刀了很多下。关于如何debug,如何构建测试样例集,希望看看dalao们的博客,看看别人怎么debug。至于我自己存在的bug,基本都是在判断输入合法性的时候,有一些情况没有考虑,换句话说,就是思维缺乏严谨性。不过这其中也有很大原因是因为,我第一次像现在这样专门为了输入合法性判断来写方法判断,多少还有些不适应,不过经过这几次作业的训练,我慢慢开始学会如何提高自己程序的鲁棒性了。
至于这些bug位置与设计结构之间的相关性……说实话,没啥关系,因为我的bug都是在输入合法性判断上的bug,全部集中在程序最开头的WRONG FORMAT检查模块里。而我的输入格式检查是与求导部分完全分开的两个部分。
重构说明:
首先在我原来代码的基础上,把普通因子类作为一个父类,在其中再加入常数类,三角函数类(不包含表达式因子),幂函数类这三个子类。我想当然把他们创建为父类子类,但回过头仔细想一想,普通因子类似乎只需要把输入的因子字符串区分成常数类,三角函数类,幂函数类这三类,然后创建并存储这三个类的对象这一个功能了,而这一功能并不需要被三种不同的普通因子类继承,因为它们要做的是求导并保存求导结果,所以不需要父类、子类的继承关系。
除此之外还有一些小的改动:把各个类中的没用到的方法去除(都是些前面作业的遗留产物),以及把不需要作为成员变量的成员变量改做相应方法的过程变量。这些小改动的基本原则是,各个类中方法只有那些实现自己这个类所必须实现的功能的方法才能保留下来,成员变量,只有那些需要被其它类获取调用或者在这个类内部被多个方法调用的变量的才能作为成员变量。举项类这个例子来说,项类实现的基本功能有两个,求导和把项切分成因子,那么留下来的方法就只有与这两个功能相关的方法。