2021面对对象设计与结构 - 第一单元
第一次作业
本次作业,需要完成的任务为简单多项式导函数的求解。
第一次的作业主要测试的是对于一些简单表达式的处理,例如如何将该表达式拆分为因子后,进行求导,然后再输出结果。在这次作业里,因为每个项里只有两种类型的因子(常数因子和变量因子),所以我采用了以x的指数为Key,以Term为Value的Map来存储所有的数据,这样也有利于输出的优化。
1. 类图
2.类/接口介绍
-
extractTerms(String, Map<BigInteger,Term>)
- 用于将String参数以项为单位拆分开。
-
findCoeExp(String, Map<BigInteger, Term>)
- 用于将String参数以因子为单位拆分开。
- 让所有常数因子相乘,让所有变量因子的指数相加,最后得到的值转换成
Term类
,并存储至Map里。
-
Term类
- 用于存储 Coe (系数)和 Exp(x的指数)。
- 该类重要接口有:
differentiate()
和toString(BigInteger, BigInteger)
。
-
differentiate() : String
- 用于实现对
Term类
求导。
- 用于实现对
-
toString(BigInteger, BigInteger) : String
- 用于将
Term类
转换成String类,以便于输出。
- 用于将
3. 基于度量的分析
很显然,可以看出有两个函数,findCoeExp(String, Map<BigInteger, Term>)
和toString(BigInteger, BigInteger)
的复杂度是比较高的,原因是在函数中用了大量的if-else和while的判断和语句。
而对于类的复杂度分析,明显看到Main的复杂度是比较高的,原因是较多的判断和分析的函数都是被放在Main类的,比如extractTerms(String, Map<BigInteger,Term>)
和findCoeExp(String, Map<BigInteger, Term>)
。
4. 优缺点分析
-
优点分析:
- 代码思路较清晰,不易出bug。
-
缺点分析:
- 由于使用了大量的判断语句和循环语句,不利于之后的维护。
- 由于复杂度较高,可能会使得之后的作业难以直接在已有的代码上作进一步的扩展。
第二次作业
本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解。
在第二次作业中,由于新增了正余弦函数的求导,显然再使用HashMap来存储表达式会过于复杂,且也不利于我之后的扩展了。所以我放弃了使用HashMap优化输出的想法,新增了Expression和Expression2类。在Expression和Expression2类里,我主要使用递归函数将表达式拆分成因子,然后进行求导,再返回结果。
1. 类图
2.类/接口介绍
-
firstExprAbstrt(String) : ArrayList<String>
- 主要用于将String参数以项为单位拆分开。
-
getFactor(String) : Pair<Integer, String>
- 主要用于将String参数以因子为单位拆分开。
-
findCoeExp(String) : Term
- 主要用于计算String类参数里的Coe,Exp,sinExp和cosExp,并转换成
Term类
返回。
- 主要用于计算String类参数里的Coe,Exp,sinExp和cosExp,并转换成
-
Term类
- 在已有的
Term类
的基础之上,新增sinExp和cosExp。
- 在已有的
-
derive(String) : String
- 主要是用于判断String函数里是否还存有嵌套的表达式,若没有则将调用
differentiation(Term)
进行计算。
- 主要是用于判断String函数里是否还存有嵌套的表达式,若没有则将调用
-
differentiation(Term) : String
- 用于实现对
Term类
求导。
- 用于实现对
-
toString() : String
- 用于将
Term类
转换成String类。
- 用于将
3. 基于度量分析
显然可以看到Expression和Expression2的类复杂度是比较高的,原因是它们两个都是针对表达式而写出的一些方法,所以互相调用的概率是比较大的。而且为了解决嵌套的关系,重复调用某函数的机率也是比较大的。
4. 缺点分析
- 大量使用了的判断语句和循环语句,不利于之后的维护。
- 由于Expression和Expression2互相调用的机率比较高,不太符合高内聚低耦合的设计标准。
第三次作业
本次作业,需要完成的任务为包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解。
在第三次作业中,主要是增加了一些判断表达式合法性和一些针对正余弦函数嵌套的处理方法。对于判断输入的合法性,我主要采用了Regex
的方式,将非法格式一一列出来(这也导致我在格式上考虑得不够全面,在强测中被hack出四个点),一旦有匹配成功的,就输出WRONG FORMAT!
。而对于正余弦函数嵌套的处理方式,主要就是在求导时,再调用一次 derive
函数对正余弦函数的因子再进行一次求导即可。
1. 类图
2.类/接口介绍
-
checkFormat(String) : boolean
- 用于判断String类参数是否合法。
-
getMap(String, HashMap<String, Long) : HashMap<String, Long>
- 主要用于优化输出。
-
getExpr(String) : ArrayList<String>
- 用于将String类参数以项为单位拆分开。
-
getFactor(String) : Pair<Integer, String>
- 用于将String类参数以因子为单位拆分开。
-
Term类
- 在已有的
Term类
之上,新增sinFac和cosFac,表示三角函数里的因子。
- 在已有的
-
derive(String) : String
- 主要是用于判断String函数里是否还存有嵌套的表达式,若没有则将调用
differentiation(Term)
进行计算。
- 主要是用于判断String函数里是否还存有嵌套的表达式,若没有则将调用
-
differentiation(Term) : String
- 用于实现对
Term类
求导。
- 用于实现对
-
toString() : String
- 用于将
Term类
转换成String类。
- 用于将
3. 基于度量分析
显然可以看到,Expression和Expression2的类复杂度依然和上个作业一样是比较不理想的。不但如此,此次的Main类的复杂度也变为不理想的了。这是因为为了检查输入的合法性和为了优化输出格式,我在Main里新增了许多使用判断语句的函数,导致相较于第二次的作业,它的复杂度大大地提升了。
4. 缺点分析
- 大量使用了的判断语句和循环语句,不利于之后的维护。
- 由于有三个类互相调用的机率比较高,所以是不太符合高内聚低耦合的设计标准的。
对Bug的测试
- 采用针对性的测试点
- 因为第一次的作业依然属于较为简单的,所以主要还是测试对加减符号和大数据的处理,例如:
-+-1*x**1+-+1*x**-1
- 第二次作业主要测试的是对表达式嵌套的处理,例如:
---(-(x**-3-(-x**-2)))
-(-(-(-(x*x*x+1)*x)*x)*x)*x
- 第三次作业主要测试的是对正余弦函数的处理,例如:
cos((x*(-sin((-sin(x))))+(-sin(x))))
cos((x*(-x)*(-x)*(-sin(x))*(x**-9)))
sin((-((-+(sin(x)*cos(x)*(-x)+9)))))
- 因为第一次的作业依然属于较为简单的,所以主要还是测试对加减符号和大数据的处理,例如:
- 采用多样性的测试点
- 有时候在写代码的时候,会习惯优先考虑复杂的表达式,而忽略了一些简单的表达式,导致小细节上出现了Bug,所以测试点的多样性是不可以少的,例如:
- 常数:
-1
,0
,1
- 求导为0的表达式:
sin(-0)
,cos(+1)
,sin(+0000000000009832342)
- 相互抵消的表达式:
x**1*x**-1
,+-+x**9-+-x**9
,sin((sin(x)-sin(x)))
- 常数:
- 有时候在写代码的时候,会习惯优先考虑复杂的表达式,而忽略了一些简单的表达式,导致小细节上出现了Bug,所以测试点的多样性是不可以少的,例如:
- 使用
Sympy
测试代码输出的正确性- 为了快速且有效的测试代码的正确性,我使用了
z3
来快速对比我的输出和Sympy
的结果,若输出正确则输出'True',否则输出‘False':
- 为了快速且有效的测试代码的正确性,我使用了
心得体会
虽然作业完成了,写代码的能力也大大地提升了,但是对于面向对象思想的实现和高内聚低耦合的设计标准还是明显感觉得出是比较弱的,因为每次的作业所做的改动还是比较大的。除了特定的类,其他的比如说derive和differentiation,虽然方法目的类似,但是改动还是比较大,重复使用性还是比较低的,就此也能看出代码的扩展性也是比较差的。因此,我总结了一些我认为写代码时,应该多加注意的事项:
- 代码整体架构的重要性
- 虽然三次作业的代码的思路看上去是较于清晰且易懂的,但是由于代码较长,可能有大部分的代码都是类似或重复的,使得代码看上去较为啰嗦。所以在动手之前,必定要先想好架构,并且规划好对所有细节的处理,否则不但重构概率大,也不利于代码以后的维护的。
- 善于利用讨论区
- 在讨论区里会有很多大神分享自己的构建方法和经验,所以多看讨论区有利于我们构建更好的代码架构,而且也有助于我们处理很多的小细节。在我的代码中,我习惯大量的使用了判断语句和循环语句,虽然这使得出现bug的概率小了很多,但却大大地提升了代码的复杂度,导致了不利于之后对代码的维护或扩展,所以在这方面还是需要多加学习的。