OO第一单元作业总结
写在前面
本学期初次接触面向对象的程序设计,三周的多项式求导作业,作业难度一次次加大,自己不断探索、思考,也有了诸多收获与感触。
第一次作业
设计思路
第一次作业功能是实现简单的多项式求导,对于刚刚入门面向对象编程的我,如何设计出一个真正“面向对象”思维的架构,是一个不小的挑战。在最初实现时,我只设计了两个类,一个是含有main入口的主类,另一个是对多项式进行各种运算。但由于对面向对象的思想理解还不够深刻,导致类的设计有些杂糅、混乱。于是,在请教了一些人有关他们设计架构的问题之后,我开始了重构代码。我把自己原来写的有些面向过程的程序根据功能上的差异分为三个类。
CheckValid类相当于一个表达式的判断器,判断输入的表达式是否合法。Main类进行多项式的输入、预处理求导以及简化输出。Poly类实现了对表达式的切分以及存储。
优点
采用了HashMap容器来盛放数据,对表达式的同类项合并等工作得到简化。系数简化、零项消去、正项提前等优化都有考虑。
缺点
没有实现对异常的捕获。整个设计还是不够“面向对象”,各个类的设计在现在看来,有界限不分明的、功能杂糅的嫌疑。由于并没有设置HashMap来存放表达式求导结果,而是生硬地取项求导,导致优化时复杂度提高,产生了bug。采用大正则匹配,再输入数据过长时,会引起爆栈。
程序结构
第一次的程序结构比较简单,复杂性较低,具体信息如下图:
bug分析
强测:未出现bug
互测:出现三处bug
(1)采用长正则匹配,导致在输入表达式过长时,会引起爆栈。独占模式也会造成一些错误,例如对于正则表达式abb*c,若采用独占模式,则无法正确匹配到表达式abbc。改进方式:采用.find()和.end()方法逐项匹配,每当匹配到一项,便从字符串中剪去它,最后判断式子是否为空串。
(2)正则表达式有一处误开独占模式,导致错误。
(3)输出时因优化条件过多,导致程序复杂性增高,产生bug。改进方式:将求导结果也用HashMap存起来,简化输出条件判断。
互测策略
首先用自己写程序时发现的容易出现的出错点去测试别人的程序,后来分析别人的代码,找出具体错误。
重构策略
构建表达式类、项类,表达式求导化简,输出等工作都由表达式类内部方法来具体实现,减小代码间的耦合性。
收获
在此次作业中,初次将面向对象的编程思想应用于实践,在设计类与重构代码的过程中,更加体会到了面向对象编程思想的精髓。
第二次作业
设计思路
第二次作业首先我沿用了第一次作业的设计模式,所不同的是自定义了类Keys分别存取x,sin(x),cos(x)的系数来作为HashMap的键值。但在实现的过程中出现了bug:每当我新创建一个Keys的对象并向其中的参数传值,我原来存入的Keys对象相应参数的值都会被改变。无法找到症结所在的我(解决过程请看第三次作业)采用HashMap嵌套结构来存相应参数。下面是向三层嵌套Map中存放(k1,k2,k3,v)的实现过程。
if (mp3.containsKey(k1)) {
mp2 = mp3.get(k1);
if (mp2.containsKey(k2)) {
mp1 = mp2.get(k2);
if (mp1.containsKey(k3)) {
v = v.add(mp1.get(k3));
mp1.put(k3,v);
} else {
mp1.put(k3,v);
}
} else {
mp1 = new HashMap<>();
mp1.put(k3,v);
mp2.put(k2,mp1);
mp3.put(k1,mp2);
}
} else {
mp1 = new HashMap<>();
mp1.put(k3,v);
mp2 = new HashMap<>();
mp2.put(k2,mp1);
mp3.put(k1,mp2);
}
优点
HashMap嵌套存入省去了Keys类的设计;省去了ArrayList中同类项的合并问题。
缺点
为优化和数据存放,求导结果存入带来了一定的麻烦。因为各个项的系数是有层次区分的而非可同时访问的,所以会为例如sin(x)2+cos(x)2这样的式子优化带来一定的麻烦。
程序结构
第二次作业的程序结构、各个类和方法的复杂度如下:
bug分析
强测:未出现bug
互测:出现1处bug
正则表达式书写有一些错误,导致互测中被hack。
互测策略
首先用自己写程序时发现的容易出现的出错点去测试别人的程序,后来分析别人的代码,找出具体错误。
重构策略
新建类Keys存放各个函数的系数,使得程序化简更为方便。
收获
将HashMap这种容器的使用应用地更加熟练。虽然写作业之中遇到了一些问题,但在思考的过程中也潜移默化地提升了自己的能力。(虽然最后换了一种方法写,没能找到bug所在)
第三次作业
设计思路
判断格式错误方法
第三次作业由于有了表达式因子和嵌套求导的要求,不能用正则表达式去简单匹配输入的字符串,判断格式。对于格式判断,我采用对字符串进行预处理和预判断当存在空串、不合法字符、不当空格、多余的加减号是,直接WRONG FORMAT!输出,否则返回去掉空格、只剩单独加减号的表达式。其余不合法情况,在递归下降时进行判断,若输入不符合预期,直接WRONG FORMAT!退出。整个递归下降的过程,我在RecrusiveDesent类中实现。 表达式存放我是用ArrayList这种容器分层存放的,Poly类中的ArrayList存放各个Term,Term类中的ArrayList存放各个Factor。Factor是因子类,根据因子可能出现的5种情况,我们设置Factor的5个子类,分别为Num、Power、Sin、Cos、PolyFactor。 整个递归下降的过程有些类似于状态机。从前往后依次读字符串的各个字符,从而构建起表达式树。表达式求导也是从上向下依次嵌套求导。 在整个写作业的过程中,我同样遇到了上次一样的问题,即每实例化一个对象,之前存入的相同类型的对象相应参数都会被改变。最后,在助教同学们的帮助下,我发现了private 和static 在类中定义变量时,若一起用,会产生一些问题。
优点
在构建表达式树前,对字符串进行了预处理,简化了之后递归下降的复杂度;递归下降分析表达式的各个字符,能够有效地简化程序的设计过程,变得易于理解和使用。
缺点
整个程序在递归下降的过程中还可以再构建加法类和乘法类,让整个程序更加“面向对象”,结构得到优化。因为表达式的复杂性,在预处理时需要全方位地考虑各种情况,否则容易出问题。
程序结构
第三次作业的程序结构、各个类和方法的复杂度如下:
Main中进行了多项式的预处理工作,所以复杂度较高;RecursiveDesent类中实现了递归下降的整体框架,因此复杂度较高。
bug分析
强测:出现1处bug
互测:出现同1处bug
在预处理过程中,有一处忘记考虑形如++之后可以跟上一个括号,导致对WRONG FORMAT!的判断有些出错。
互测策略
在此次作业中的互测环节,我首次使用Python的对拍器对大家的程序进行检测。在bug检测的初始阶段(套用易于出错的数据)节省了不少的时间。随后读代码,寻找bug。
重构策略
表达式预处理应单独设置一个类进行,区分好各个类的功能。对于递归下降应设置加法类和乘法类,降低程序一个类中的复杂度。
收获
在此次程序中首次用到继承与抽象的概念,特别是因子类的继承与抽象,对于简化程序起到了很关键的作用。
结语
随着OO第一单元的结束,我也对面向对象编程这一种设计方式有了自己的感悟与理解,在之后的学习过程中,我会多读代码,勤学善思,争取在这一课设中取得自己满意的成绩。没有对象也要面向对象好好编程。