OO - Unit1总结

总览

使用MetricsReload插件对我的项目进行分析,得到类的复杂度如下图所示,整体来看,主要复杂度高的地方在第二次作业和第三次作业迭代的地方,这也与我设计上有所欠缺有关。
OO度量

架构优点

  • 分类明确。每个类都处理好自己的工作,相互没有过多交集,所有的get方法我都是返回一个映射而非返回类成员本身,符合面向对象封装的思想。
  • 可迭代性好。整体的迭代工作并不困难。

架构缺点

  • 可读性差。在函数因子FuncFactor与函数定义FuncCall里面,在参数处理方面,我写了很大的if - else分支,导致了代码可读性很差,而且这样很容易漏掉一些情况,这也与我强测寄了有一定的联系
  • 重复性高。出于一些原因其实就是懒,我没有对代码进行重构,导致在递推函数FuncFactor和普通函数SimpleFuncFactor里面存在大量的代码重复,没有提炼成合理的方法,确实是不可取的地方。

类图如下

类图

第一次作业

设计思路

设计思路上,我套用了之前OOpre的第七次作业给的递归下降的模版,从表达式逐层下降解析,直到把整个表达式解析完成。在预处理和最后的化简阶段,我使用了一些正则表达式的知识,比如匹配并去除空白字符、匹配多余的+号,还有在输出之前去掉多余的^1等等。

而关于中间的单项式与多项式,第一次作业表达式的结果一定是
$$
\displaystyle \sum a \cdot x^n
$$
所以,运算中的单个结果一定是
$$
a \cdot x^n
$$
因此,我构建了一个类名为Unit,还有一个类名为Poly,用来处理单项式与多项式。而表达式、项、各种因子里面也有一个toPoly方法,用来把已经解析完成的表达式转换成多项式以供运算和输出。

优化思路

本次作业整体比较简单,除了删去多余的^1、乘号等,我还发现,可以尽量把正项放在前面,这样就可以省略一个+的长度。除此之外我没有想到什么可行的优化方案。事实也证明是这样,在强测中,我得到了100分。

hack思路

本次hack是比较艰难的,因为大家好像都没有什么bug。我在hack中多数依赖随机生成的测试点进行测试。只有一位同学的代码让我的评测机产生了报错,但是在将测试点复制到IDEA里面进行本地运行的时候却没有问题。直到后来我在观察这位同学的代码时,我才发现这个同学在处理输入的最后一个空白字符时会产生空指针,而我在复制测试点时只会复制到最后一个有效字符,确实是疏忽了。

第二次作业

设计思路

本次作业可谓是一个滑铁卢。在最开始设计的时候,我没有想到什么特别清晰的处理方法。最开始时,我试图使用字符串替换递推函数的方法,中间使用正则表达式来匹配。但是经过了一段时间的努力之后,我发现这样的做法存在着很大的弊端。于是后来,在和朋友们交流之后,我重新构建了递推函数的部分,先使用静态方法把定义式解析后,记录下对应位置的形参,然后在递推函数因子的toPoly方法里面,将形参和实参进行替换并计算。整体的思路是比较清晰的,但是因为时间原因,也没有进行完整的、足够的测试,最终少考虑了很多情况,在强测中得到了比较低的分数。

但是上次的设计思路依旧可取,不过形式需要转换一下,毕竟加入了新的三角函数等。然而在合并同类项的时候,我的设计整体比较奇怪

Unit

如图,明明Poly是由Unit组成的,但是Unit的成员里面却有着Poly,导致我在判断两个三角函数表达式是否相等时出了点问题,我重写了几次hashCode方法效果都不理想,最后干脆直接toString比较字符串了,效果居然还不错,但是在遇到一些刻意的不同形式的等价表达式,这种判断方法就不够用了。

debug思路

本次作业最主要的问题是情况考虑的不周到,忽视了递推函数中参数为三角函数因子的情况。所以在最终的debug过程中,我做的最多的其实是补充if - else分支,解决被忽视的情况。另外,我还更改了上次作业遗留的问题。在上次作业中,如果在表达式开头或者(之后直接跟了一个负号,我会主动添加一个0,把它改编成0-但是这样存在问题,如果函数因子为一个负常数,会导致这个负常数变成了一个表达式,进而解析就会出现问题,所以最后是把解析常数因子的部分修改成可以判断负号了。

第三次作业

设计思路

设计思路依然延续上两次作业的架构,新增了求导因子和普通函数因子。普通函数因子仅在最初定义时和递推函数有所不同,所以其实可以再把二者定义一个接口,把方法再精炼一下。

求导的实现也是使用了递归的思路,我把求导的方法添加到了Factor接口里面,同时也添加了clone方法,实现深克隆,避免修改原来实例化了的类。

debug思路

经过了上一次作业的痛苦教训,我尽力考虑周全了所有的情况,然而在一个不起眼的地方,我失误将返回的类定义为了Expr类。然而在我的处理中,处于内层的表达式都应当使用SubExpr类,所以当出现递推函数调用普通函数进行定义的时候,会产生参数替换失败的情况,也就是f{1} = g(h(x^2))这样的情况,我会解析成f{1} = g(h(x)),产生了一些bug,使我强测失去了一点分数。

hack思路

在hack开放之前,我先列下了一众大家可能会忽视的输入形式,比如函数嵌套调用、对三角函数嵌套求导等,然后人工捏了很多压力测试点,在hack开放后,对整个房间里的所有代码进行同步测试,最后确实发现了很多错误。

可能的迭代需求

可以发现,有很多的表达式形式都没有考虑到,比如指数函数、对数函数等等。以指数函数为例。两个底数相同的指数函数在相乘的时候,其底数不变、指数相加,那么在多项式乘法的方法里就要增加对指数函数相乘的处理:

  1. 判断底数是否相同
  2. 将指数进行相加

对数函数的处理也是类似的。

优化

本次作业我没有做特别多的优化,主要的优化在三角函数特殊值判断上面,比如运算中出现sin(0),cos(0)等等,另外我尝试做了一下cos^2+sin^2的优化,不过只能在比较简单的条件下进行判断。

心得体会

本次作业确实让我体会到了面向对象编程的奇妙之处。荣文戈老师的理论课也让我启发颇多。封装、多态的思想确实是面向对象的重要特点,也是面向对象编程有趣的地方。表达式的解析、递归下降,把每一项任务逐步下放到每个类中,每个类不向外界展示自己的“隐私”,让代码的运行变得清晰而有迹可循。

未来方形

对于课程未来的方向,我觉得可以适当给予更多的时间以供大家对自己的架构进行更充足的优化,对自己的代码予以更充分的检查。另外,建议课程开展一个类似额外补充的项目,指导大家如何搭建属于自己的评测机,可以给一些合理的数据生成思路等。