OO第一单元作业总结

文章介绍了作者在OO课程第一单元的作业中,对Term类的设计,包括为何选择这种设计,开发历程,Bug分析及解决策略,以及心得体会。作者强调了系统化和规范化设计的重要性,同时也分享了在迭代开发中的经验,如如何处理三角函数和求导运算,以及在重构和迭代过程中的思考。
摘要由CSDN通过智能技术生成

OO第一单元作业总结

目录

OO第一单元作业总结




一、整体分析

1.大致介绍

在这里插入图片描述

在我的设计中,设计主体是 “项Term”和“解析器Parser” ,其余类主要起辅助作用

不同于教程推荐的“Factor接口+Num/Var/Expr/Sin/Cos”的设计,我将Factor下属的可能的类直接存储在了Term中,这样一来也就自然不需要Factor这个接口了。

具体地讲,类图中Term的“系数Cofficient”即为一个项中的数字部分,“指数IndexOfX/Y/Z”分别表示x、y、z的指数,“表达式Exprs”为存储Expr的数组,“三角函数Coss/Sins”为存储三角函数的两个数组。

Term
Cofficient
BigInteger
IndexOfX/Y/Z
Sins/Coss
Exprs
Expr


同时,我没有使用“Lexer”,这是由于早期设计中,对于递归下降理解有限,为了避免Lexer于Parser之间的交互问题,我将Lexer集成在了Parser之中,两种方式我认为差别不大。



2.为什么采用这种Term设计?

我的Term类的设计的好处在于,直接将Factor接口集成在了Term类中, 避免了在进行运算时的类的上下转换。
例如, 在运用Factor接口的设计中,如果要将Term与x相乘,我们需要首先遍历Term的Factors,寻找Term中的变量x,对其进行深拷贝,然后在乘法后返回一个新的变量,加入到Factors中,并删去原有的变量。(当然,也可以直接更改原变量x,但是这样做在之后的扩展中未必安全)

对此,我的评价是:系统化、规范化但实现复杂

在我的这种设计中,将Term与变量x相乘只需要直接将“IndexOfX”加一。

之后在第二次的作业增量开发中,我为Term新增了两个Expr数组,分别存放sin、cos括号中的因子,直接避免了创建新的三角函数类。而对三角函数的具体处理,也只是表达式的化简和比较,都是第一次作业已经实现的功能,大幅简化了工作。



3.其余几个类的设计考虑

首先说Parser
我的Parser实际上是“Lexer+Parser”,一人干两份工,这或许是一个设计上的失误,因为我设计开始时还没有系统地了解过递归下降。不过这样也省去了Parser和Lexer的交互,而且也没有出太大的问题,所以之后也没有改动。
在具体实现方面,Parser就是负责解析输入数据,并以Term为单位进行存储。在每个Term的新建过程中,首先初始化一个基础的Term,然后依次去乘每一个用乘号分割的部分。

输入Parser
遍历解析Term
未检测到括号
检测到括号
未化简表达式
ParseExpr
ParseTerm
得到Term并返回
得到Expr

之后讲一讲“Process类”
这个类算是比较取巧的一个类,主要作用是把输入的表达式预化简到一个“符合日常使用规范”的样子。
在本单元的作业中,输入的数据很多并不符合我们的日常习惯,例如-+-001、-x*-003以及许多空白符。因此,我使用一个工具类来把表达式预化简,使用正则匹配来循环匹配连续的加减号并合并、匹配前导零并去除、去除空白符……此外,为了减轻Parser的工作量,这里还把乘方预展开了,这样Parser就不需要考虑乘方的情况。

最后我想讲一讲“Derivation类”
这个是第三次作业的求导类,求导的具体思路如下:

遍历Term
对内部表达式求导
寻找求导运算
化简要求导的表达式
DerExpr
DerTerm
对x/y/z求导
对sin/cos求导
变更所在Expr数组

二、开发历程

1.第一次作业

第一次作业是从无到有的开发,也是最费时费力的一次作业。
在最初着手开发时,我参考了课程组给出的建议,设计了Factor接口和下属的Num、Var类。

随后我遇到了第一个难点——类的上下交互
我最初的设计是这样:

遍历两个Expr的Term数组
遍历两个Term的Factor数组
Expr*Expr
Term*Term
Factor*Factor
Num/Var*Num/Var
Expr*Num/Var

但是,这样的工作量太大了,要设计多种乘法,而且还牵扯到类的上下转换,太过复杂,容易出错且不容易找bug。

于是,我马上进行了第一次重构

在新一次的设计中,我采用了现在的Term设计,流程就得到了大幅度简化:

1.两者Expr数组非空
遍历两个Expr的Term数组
2.某个Expr数组为空后
Expr*Expr
Term*Term
系数相乘,指数相加,返回Terms

之后,第一次作业的最大难点便解决了,之后只需要进行合并同类项即可,而这种设计中,只需要比较两个Term的指数是否相同即可。



2.第二次作业

在第二次作业的迭代开发中,我新增了Func类,用来存储自定义函数,并使用字符串替换的方法将输入的表达式预处理,这样一来,第二次作业和第一次作业的区别就只有三角函数了。

对于三角函数,我的处理如下:

ParseExpr
括号前无三角函数
括号前为sin/cos
识别括号
Expr
加入Term的Expr数组
加入Term的Sins/Coss数组

至于三角函数的化简合并,我只做了最简单的处理,并未加入各种复杂的公式。



3.第三次作业

在第三次作业的迭代开发中,由于我的第二次作业已经可以处理自定义函数定义的嵌套调用,于是只需要考虑求导。

为此我增加了求导工具类“Deviration”,具体实现在第一部分已经介绍,不再赘述。



三、Bug分析与Hack策略

1.我的Bug

在第一次作业中,我忽略了指数的前导零带来的影响,导致在计算指数的时候出现错误,最终解决方案为改为使用Integer.ParseInt()来解析指数,避免了前导零的影响。

第二次作业在设计之初,由于深拷贝和浅拷贝问题,导致了三角函数的指数化简出现问题,最终自己写了递归深拷贝方法来解决问题。
在强测中,由于括号检测的优先级问题,导致三角函数括号不对应,更改后解决问题。

第三次作业未发现Bug。



2.Hack策略

第一次作业中,着重检查了连续符号、前导零、大数运算这几个点,最终Hack成功6次。

第二次作业中,配合简单的评测机,着重检查了三角函数和自定义函数的相互嵌套、三角函数和自定义函数的指数运算、无意义多重嵌套括号等,最终Hack成功5次

第三次作业中,配合简单的评测机,着重检查了嵌套三角函数的求导、函数定义求导和相互调用、自定义函数和三角函数以及求导运算的嵌套,最终Hack成功4次



3.测试小结

在测试过程中,我注意到“单元化测试”的重要性,即完成某一部分的功能后,立即进行单独测试,这样可以及时发现错误,避免功能复杂后难以Debug。

同时,在Hack过程中,我也注意到部分同学进行了更多的化简运算,取得了更高的性能分,但也因此出现了Bug导致被Hack,这或许是对应着“多写多错”,在这一方面,应当更加注意测试与思考,避免因小失大。



四、心得体会

1.论规范化与系统化

这一单元的作业中,最麻烦最困难的在我看来反而是第一次作业,一方面是假期刚过,需要进入状态,另一方面是从零开始,码量和思考量都比较大。以至于,第一次作业大概花费了20到25小时,用了两天的时间才写完,最终成品是450~500行。这是最艰难,最折磨的部分。

当然,最终在第一次作业的实现中,也并不是很“面向对象”,这既与题目要求和设计有关,也和我思维的转变有关。

前面也说过,第一次作业设计之初,是希望非常系统、规范地定义各个类和方法的,但是无奈最终实现太过复杂,让人望而生畏,于是我在第二天上午一边继续完成,一边重新构思,然后在中午放弃了之前一天半的设计,然后用一个下午完成了现在的版本。(所以实际上或许,这次作业的完成时间大概在7~8小时)

关于这一点,也是我最大的体会,就是系统化带来的是高可读性和高可拓展性,而简单化带来的是快速的完成度和简易的上手度

在此之后,我当然也会再进一步去学习系统、规范的方法,但是要我在第一周就完成这样的设计或许真的不太现实。



2.关于迭代开发

在进行第二次作业的设计时,我认真考虑了迭代开发工作的进行。

我认为,重构的情况可以有,但是大规模重构显然是有问题的:要么是第一次作业可拓展性太差,属于架构不够合理;要么是迭代工作不到位,没有认真考虑迭代工作。

关于迭代开发,我当时也在讨论区发布了一个帖子。简单来讲,就是我希望通过“封装复用”以及“递进开发”来进行迭代工作。 而事实上,这种思想也卓有成效,让我在二、三次作业中均避免了重构,且减少了工作量。

最终成品,第二次作业代码量是700行,用时5小时左右(但是后面Debug也花费了一些时间);第三次作业代码量是900行,用时4小时左右。

当然,在代码量上,由于部分方法没有进行单独抽象,实际上复制粘贴也占一定的比重。这种行为是不规范、不合理的,在之后的设计中我也会尽量避免这种偷懒行为。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值