面向对象第一单元博客总结
前言
在第一单元的面向对象编程练习中,我们通过对于多项式求导的一步步扩展,对于类与对象、以及类的层次化和抽象拥有了更为深入的了解。由于此前对于面向对象编程语言没有很多的编程经验,因而在具体的程序实现过程之中难免走了许多弯路,但在这几次作业中也着实收获了许多,仅以此博客来对第一单元的所学所感进行一次较为粗浅的回顾,也算是对这些许心得的一些总结。
第一次作业
程序结构分析与度量
第一次作业较为简单,因而类的数目较少,之间的关系也比较单一。程序结构如下图所示:
不难看出,在多项式类中包含由若干项组成的Arraylist
,以及对多项式求导与输出的方法;在项类中包括项对应的系数与指数,以及对项合并判断与求导的方法;最顶层的求导类则包括对于输入输出的正则表达式处理。
对代码性能进行分析,我们可以得到下表
class | OCavg | WMC |
---|---|---|
Derivation | 4.00 | 24 |
Polynomial | 4.75 | 19 |
Term | 1.09 | 12 |
Total | 55 | |
Average | 2.62 | 18.33 |
由于本次作业中的多项式结构比较简单,因而本次作业的主要难点在于对于输入输出的处理,复杂程度最高的两个方法分别是Derivation
类中的pattern1Analyze
方法以及Polynomial
类中的printPoly
方法。由于对于问题的抽象与分解能力不足,对输入输出的解决方法比较粗暴,这个问题也一直延续到了后续的其他作业。
BUG分析
第一次作业的bug出现在一些自己没有考虑到的边界条件上,由于自己对于题目的解读不甚仔细,且第一次参与这种只有一次机会的强测练习,没有在课下进行全面系统的测试,导致在强测中因为格式问题挂掉了三个点,但是其实都是微小的细节问题,十几分钟就全调完了。主要问题如下:
- 没有考虑空白字符出现在多项式首部的情况;
- 对于合并后系数为0的项在输出时未正确滤去。
随笔杂记
本次作业中,处理输入是一个比较关键的问题。为了使程序能够处理较长的输入,我采取了逐项匹配的方法,利用`matcher.find()
以及每一次的匹配是否以上一次结尾为起点为评判正确匹配的标准。此外,为了正确的辨别空白字符是否处于其应处的位置,我在匹配的正则表达式中加入了许多 \\s+
,然而这样会使许多违法不可见字符成为漏网之鱼,于是在具体处理每一项时,我将所有的合法空白字符都去掉,再对整个项进行完整匹配,以判断是否存在非法空白字符。
第二次作业
程序结构分析与度量
第二次作业在第一次的基础之上加入了三角函数,由于当时不知道第一单元的具体教学内容,内心估量着可能这是多项式的最后一次作业,就在设计架构时选择了较为省事缺难以扩展的架构,将三角函数依旧特化为一种特殊的系数,并通过
\[ (x^asin^b(x)cos^c(x))'=ax^{a-1}sin^b(x)cos^c(x)+bx^asin^{b-1}(x)cos^{c+1}(x)-cx^asin^{b+1}cos^{c-1}(x) \]
来确定最终的求导结果。程序结构如下图所示:
不难看出,此次作业与之前架构基本相同,只是在第一次作业的基础之上增加了三角函数因子的系数,以及一些针对三角函数的等价优化。此次作业的代码性能分析如下表所示:
class | OCavg | WMC |
---|---|---|
Derivation | 4.67 | 28 |
Polynomial | 7.20 | 36 |
Term | 1.50 | 30 |
Total | 94 | |
Average | 3.03 | 31.33 |
此次代码写的比较复杂的地方仍然集中在输入输出处,此外复杂度较高的部分还有对输出多项式的优化。输入基本延续了上一次作业的遗留产物,输出虽然做了一定程度的改进但还是相对较冗余,优化部分虽然只对\(sin^2(x)+cos^2(x)=1\)进行了基本优化,但是还存在许多不合理的地方,后续会稍加讨论。
BUG分析
有了第一次作业的经验,第二次作业在课下进行了比较全面的测试,最后在强测时完全正确,由于16级高工没有互测环节,因此也无法保证最终结果完全没有bug。一个可以算是bug的地方在于,在优化时考虑的优化条件较为单一,因此在一些情况下反而会出现负优化的结果。正所谓简单与通用难以得兼,可性能分甚至比一些没做优化的同学还低,也不免有些苦恼。
随笔杂记
第二次作业在第一次作业的基础上,增加了三角函数因子,也便引入了性能方面的优化问题。在本次作业中,我采用了较为简单的优化逻辑,对于\(sin^2(x)+cos^2(x)=1\)形式的多项式进行了较为简单的优化。优化逻辑也比较直接,即寻找其他次数相同,系数符号相同且\(sin(x)\)与\(cos(x)\)次数相互差2的项进行一次次合并,直到最终得到的表达式不变为止。这种做法的优点是可以对多个层次的\(sin^2(x)+cos^2(x)\)因子进行依次合并,缺点则是考虑的范围太少,没有考虑\(1-sin^2(x)\)这种结构的优化,并且对于在运算过程之中恰好消去的常数项,无法拆分给需要合并的其他项。
第三次作业
程序结构分析与性能度量
第三次作业是接触面向对象编程以来写过的最为复杂的一个程序,同样也因为经验的不足,导致了对架构把握的不是非常的理想,出现了一定程度上的冗余。程序架构如下:
本次程序由两大部分构成,一是负责处理输入的词法与语法分析,此处参考了编译课程的相应设计,另外则是对于不同种类项的处理,建立抽象类Function
以实现对于常数、幂函数、三角函数、加减、乘法以及嵌套的各种子类实现。在求导时,只需要按照语法分析得到的类似树形的结构进行递归求导,即可轻松的计算出正确结果。但由于第二次作业几乎为0的扩展性,本次作业可以说是完全推翻了前两次设计好的架构,从零开始重新搭建了架构,代码性能分析如下:
class | Ocavg | WMC |
---|---|---|
AddSubFunction | 3 | 9 |
ConstFunction | 1 | 4 |
Derivation | 2 | 4 |
ExprFunction | 1.6 | 8 |
Function | 0 | |
Grammar | 4.75 | 38 |
Lexical | 2.4 | 24 |
Lexical.SymType | 0 | |
MulFunction | 2.66 | 8 |
NestFunction | 1 | 3 |
PolyFunction | 1.66 | 5 |
TriFunction | 2.33 | 7 |
Total | 110 | |
Average | 2.5 | 9.16 |
可以看出,除去语法分析处理输入的部分复杂度较高,其他部分由于层次化的设计已经较前两次而言拥有了较好的性能。不过本次作业由于较为复杂,便没有在优化方面进行过多的涉及。
BUG分析
想不到这次作业又重蹈了第一次作业的旧辙,栽在了对于输入的处理上。由于语法分析不像前两次作业打磨的那样细致,因而有一些Wrong Format还是成为了漏网之鱼,导致了程序抛出异常。这个故事也再一次提醒我去使用try catch结构,之前一直都没有对抛出异常进行过相应的处理,以后的作业里一定在这方面加强重视。
应用Creational Pattern
其实在本次作业中我已经粗浅的使用了较为原始的工厂模式,只不过由于没有系统的学习过相关内容导致写得比较粗糙。我实现了一个名为Function
的抽象类,内含两种抽象方法diff()
和print()
。通过创建不同类型的子类,我实现了对于不同类型函数的求导,由于他们共同来自于一个父类,因而可以非常方便的实现各种类型求导结果,并将他们统一在一处。
但由于求导其实并非各项自身的一种属性,而是一种通用的方法,因此也许用接口来进行一个统一的实现或许会更为合理。一种可能的实现思路是实现统一的求导以及输出的接口,通过实现一个FunctionFactory
的工厂类来对这些不同的项进行求导操作的统一管理,以达到更为规范的目的。
随笔杂记
其实这次作业的架构在后几天的时候是发现了一个非常致命的冗余点的,即ExprFunction
与
AddSubFunction
的功能重复了。作为前两次作业的前朝余孽,我想当然的认为表达式因子一定要由一个ArrayList
构成。后来我在最后阶段的调试过程中发现,其实可以通过加减因子的复合来实现表达式,并且可以更好的来进行项之间的合并,在一定程度上简化输出。然而改进的版本在课下弱测的第四个点WA了好久,自己尝试多次还是无法精确定位问题出处,只得提交原始的粗糙版本,可以说是一个不小的遗憾吧。
小结
第一单元的学习已经告一段落,我对面向对象的了解也在进一步的加深,由于每一单元的作业都是一次次的循序渐进的过程,在未来实现更为复杂的需求时,一定要考虑好怎样保留相应的扩展性,为未来的自己多留一条生路。希望自己在面向对象的学习道路上能够走的更加平稳。