2021年春-OO-第一单元总结

2021年春-OO-第一单元总结

前言:

本文是2021年面向对象第一单元总结,主要内容是进行表达式求导,分为三个阶段,从简单表达式求导,简单三角函数和括号嵌套,再到三角函数内表达式嵌套以及格式检查,是一个迭代开发的过程,我起初使用了正则表达式匹配的方法,但在第二次作业中受挫,因此重构了一次,但没有使用递归下降的方法,而是正则+递归的方式进行。下面我对这几次作业进行一下总结与分析。

在完成过程中,有许多同学给予了我很多帮助,在此感谢这些同学:)。我也在完成作业的过程中体会到了面向对象的一些优点。


一、基于度量来分析程序结构

1. OO思想

我之前接触过C++的面向对象的特性,一直对封装、继承、多态有所耳闻,但又敬而远之,鉴于没有一些实例来让我们去应用一下面向对象的特性,我一直不能理解面向对象的优点。而在这三次作业中,我真正实践了面向对象,也体会到面向对象的一个好处——版本的可迭代性,这也是在大型工程中所必要的,也是我所欠缺的。如果面向过程来写,必然会在一次次功能更新,版本迭代中,变得冗长,而且如果对某个对象进行增删,很可能牵一发而动全身,一座代码大厦很可能崩塌。

所以,要想写好这些程序,面向对象的思想是必不可少的。我所完成的代码,第一次作业只是部分面向对象,大部分操作没有封装,而是在Expression的构造函数内完成的,完全没有考虑括号的问题。在第二次作业引入了括号,导致我的代码直接崩溃,因此不得不重构,使用类似递归+正则匹配的方式完成,并且将项的每个因子都建立了类,对格式检查建立了独立的类等等。

展望,我第一单元作业虽然已经完成,但是我对继承的应用是比较少的,由其他同学得知,其实我们可以对所有因子定义一个接口,里面定义求导方法等等,这样我们就不用管项的内部是什么类型的元素,只要直接使用同一个接口求导即可。这是我所没有实现的。未来应该更多利用继承和多态的思想。

2. 第一次作业

使用XMIND画出类的属性和方法:

image-20210330115416316

每个类的代码总规模:

image-20210330115845696

每个方法(主要方法)的复杂度:

image-20210330120538297

每个类的复杂度:

image-20210330123118242

第一次作业面向对象思想较少,我仅仅封装了两个类,一个Item项类,一个是Expression多项式类,然后在MainClass的main中构造Expression对象,大多数操作是在Expression的构造函数中完成的。

大致思路:

我使用了正则表达式匹配每一个(因子),然后将每个因子存为Item,使用analyse方法来判断是什么因子
并且用链表来存储每一个Iterm。

上述正则表达式为regItem = "(\\-|\\+)?((\\d+\\*)?x|\\d+)(\\*\\*(\\-|\\+)?\\d+)?";
    
然后如果发现两个因子之间是用乘号连接的,就使用multiMerge来合成因子。
然后开始合并同类项,指数相同的就用addMerge合成。
最后输出时,调用Item的differential方法进行求导,然后用toString进行输出。

复杂度分析:

  • 再次审视这些代码,感到有些陌生,检查上面的复杂度表格,我们发现Expression的Ocavg(平均方法复杂度)较高,是因为我的主要操作(正则匹配表达式因子,合成因子,合并同类项,toString)都是在Expression的构造函数完成的,导致其复杂度较高。

  • 观察方法的复杂度,也主要集中在Expression的构造函数和Item的toString函数。

  • Expression的构造函数的核心复杂度也较高,就是因为上述原因。

  • 而toString函数的ev(G)(核心圈复杂度)、iv(G)(方法设计复杂度)、v(G)(圈复杂度)较高,原因是我为了简化结果,比如对于1*x,简化为x,为了使得输出更加简洁,我使用了大量的if else 语句,造成设计复杂度变高。

优点:

相较于他人,我的思路更加简洁(虽然可迭代性不高),如果将每一步封装成一个方法,或许能降低复杂度。

缺点:

代码要求高内聚和低耦合,高内聚的思想在代码中并没有体现。即没有使用工厂模式,没有定义因子的统一接口,使得我必须在Item内使用analyse函数进行分析每一个因子的含义,然后存入对应的地方。

3. 第二次作业和第三次作业

第二次作业我的代码进行了重构。接着第三次作业沿用了第二次作业的代码,仅更改了CheckFormat和三角函数读取。如下所示。

XMIND画出类的属性和方法:

MainClass

每个类的代码规模:

image-20210330174412648

每个方法(主要方法)的复杂度:

image-20210330174701149

每个类的复杂度:

image-20210330174724172

大致思路

  • 我这次代码重构,使用的是正则匹配和递归。相对于递归下降的方法,较为麻烦一些,并且并没有对所有因子定义一个同一个接口,而是交给了Term来处理所有的因子。对于一个Term项,我使用coe存放系数,exp存放指数,List<Expresion>expressions来存放表达式单元,用sin和cos对象存放三角函数,sin和cos对象都有俩属性,一个是指数,一个是表达式。
  • Main函数中,首先读取字符串,然后统一多次检测错误格式。之后利用工厂Factory类来读取表达式。然后展开表达式,求导表达式、最后输出。
  • Factory类内有两个方法,一个是getExpression(),一个是getTerm(),它俩相辅相成。
  • 在getExpression()中,会循环地调用getTerm()来拼接表达式。
  • 在getTerm()中,我使用了正则进行匹配每一项,匹配到因子,就直接替换掉该因子,判断因子的类型是在Term的add函数中。碰到括号和三角函数,就再次调用getExpression(),这样一直循环下去。

复杂度分析:

  • 其实我的代码耦合度还是很高,各种操作直接比较交错,没有分离,没有高内聚,复杂度较高。

  • 首先是Check类的复杂度OCavg(平均方法复杂度)比较高。我认为主要原因是我使用正则表达式匹配每一个错误格式(si n &x**x),然后使用栈来寻找表达式的括号是否匹配,检测三角函数内是否有非表达式的多因子乘法。这些大都用了循环来寻找,复杂度较高。

  • 接着是Factory,其承担了主要的读取表达式的任务。但是由于我没有统一定义因子的接口,导致我需要分类来读取各种因子,项,使用了很多ifelse语句来分类。

  • 具体到某个方法,Term的toString方法是复杂度是很高的,我认为非常重要的原因是,我的一个Term存放的是所有因子(表达式因子,三角函数,指数函数),而toString方法一次性将所有的因子进行输出,需要管理许多事情,比如是否是开头,是否带括号等等。因为我没有定义每个因子的类,所以我不得不一个个去实现他们的toString,复杂度很高。

  • sinCosCheck方法的复杂度很高,主要原因同Check类。使用了栈和大量循环。

  • getTerm()方法的复杂度也很高,主要原因同Factory类,我使用很多ifelse语句来适配每一项因子。

优点:

思路简单而清晰,具备了一定的可迭代性。使用了近似的工厂模式。

缺点:

仍然没有定义因子的统一接口,使得我必须在Item内使用add函数进行分析每一个因子的含义,然后存入对应的地方。

二、分析程序的bug

在三次作业中,我都被发现了bug。大多数为同质bug,下面进行分析。

  1. 第一次作业的bug是,我初始时使用了替换+-+-的方法,但是我虽然替换了四次,但是忘记将结果存放为第四次替换的结果,导致存在+-的问题,读取没有考虑,导致了错误。

    String ans4 = somethint.replaceAll("+");
    String ans5 = something.replaceAll("-");
    String str = ans4;//类似这样
    
  2. 第二次作业的bug,是我在展开表达式的过程中,每次找到Term,与其存储的每一个表达式的项相乘。会删除该Term,而我在list链表中操作,导致每次删除元素后,i++,i处的元素没有忽略了。需要在删除Term后,i--来对抗for循环中i++。

  3. 第三次作业的bug是链表的深拷贝问题,这确实是个大问题,因为我使用链表存储每一个表达式,而表达式内的每一项都是存储着表达式的。而我初始时,只是拷贝了两层List,导致如果嵌套括号太多,拷贝时,第三层List指向了同一个对象,导致改变这里时,另一个地方也发生了改变。

  4. 另一个bug时,大意了。由于题目要求指数小于等于50,我使用了int来存储指数,导致遇到超长指数,无法throw我定义的Exception,而是报错了。

三、互测的bug分析

我hack较少,其实比较容易出错的是那种嵌套很深的。

sin(cos((x+sin(sin(x)))))

这使得一些浅拷贝的人出现链接问题。

对于互测的内容,我花费的精力较少,需要在以后的学习中学习别人的好的构造bug方法。

四、重构经历总结

image-20210330115416316 MainClass

​ 鄙人在第二次作业的时候进行了重构,因为我在第一次作业过程中,没有考虑括号的问题,仅仅按照老师的引导,使用了正则表达式进行匹配每一项,导致我出现括号时,没有办法进行迭代更新。所以我在周日上午进行了重构。使用了类似递归下降的方法,但是仍然使用了正则表达式进行匹配。同时代码更加完善了。

​ 可以由类图看到,我构建了更多的类,代码封装性更好了,而且初步定义了sin和cos等三角函数类,降低了耦合度。Expression和Term的目标更加明确,独立除了Factory类来读取表达式,有效减少了Expression构造函数一次完成的乱象。

​ 其实,重构后的代码仍然没有达到我的要求,我其实更希望继续进行封装,提高内聚性,让Term不再处理每一个因子的具体事务,应该交给每一个因子去处理求导和转换字符串等。

​ 重构后的代码支撑了我后两次作业。我虽然学的不太好,但是感受到了面向对象的灵活和处理迭代问题的优点。

五、心得体会

我在前面已经说了好多感想了/(ㄒoㄒ)/~~。
此次作业,我总的来说,让我学到了好多,比如工厂模式,深拷贝,继承和多态的应用。同时体会到了迭代开发的难处,体会到了工作后遇到这样的甲方的难处。要想处理好迭代开发,必须首先思考这样的程序未来的样子,眼光长远一些,并且做好封装,对于每个类,仅留部分的接口供外界调用,减少不同类之间的交叉,让类与类之间的关系更加清晰,相互之间仅留必要的部分供交互。

同时,接口的重要性我仍然没有进行实践,我未来将会增加接口的使用以及继承的使用,让同一类型的东西更加整齐。

虽然活着很没意思,但是学习到许多有用的知识,仍然让我受益匪浅,我未来将更多地利用OO的知识来思考和处理问题,方便未来可以好一些。

感谢阅读,祝生活愉快。😃

posted @ 2021-03-30 12:50  imingx  阅读(150)  评论(2)    收藏  举报