OO_Unit1总结

OO_Unit1总结

㊙️ 第一单元的主要任务是实现表达式的计算和化简,从第一次作业到第三次作业表达式结构越来越复杂。总体思路是采用递归下降的方法处理,主要环节有:解析、分类、计算、优化。下面将进行简单介绍。

题目概述

  1. hw1:对只含有单层括号,支持加减乘方的简单多项式进行拆括号和化简输出
  2. hw2:新增三角函数、自定义函数和求和函数,允许括号嵌套。需要对自定义函数进行代入,解析三角因子和求和因子
  3. hw3:允许三角函数内套娃三角函数

架构设计

分析:
🚣🏻‍♀️对于第一次作业,要求较简单,结果必然是若干x的幂的和,于是笔者采用HashMap<指数(Integer),系数(BigInteger)>的容器运算和存储结果;
🚣🏻‍♀️对于第二次作业,由于增加了三角函数,所以简单的BigInteger数据类型并不能表示x的系数(将形如sin(x)**2的三角因子与简单的数字一并看成x的系数)。笔者新建了用于存储幂函数系数的容器类Tuple;
🚣🏻‍♀️对于第三次作业,笔者第二次作业的系数容器类不支持三角函数套娃,于是又双叒重构了Tuple类以支持新的系数结构。


hw1

  • 解析:由解析类Parse完成 依照Expr(term±term) - - > Term(factor*factor) - - > Factor(consFactor/ varFactor/ ExprFactor)的层次递归下降(遗憾的是在hw1笔者并没有把他们抽象为类的想法),将解析结果存储在static类型的列表中
  • 输入处理:由输入类Input完成 Input的函数将根据解析结果为每个操作分配操作行为(Operation)对象,操作参与者(Operator)对象。 例如:对于f3 add f1 f2,新建操作行为对象new Operation(id: f3, type: add,parameterOps: {operator1(id=f1), operator2(id=f2)} ) 操作行为结果对象:Operator(id: f3, bottom: false ,coefficient: null) 操作参与者对象(已存在)
  • 运算:由Operation类和Operator类共同完成 其中Operation调用当前的操作行为的id和运算类型,使操作参与者实现相应的运算方法,将结果返回给操作结果变量的系数属性。具体过程如下:

     

  • UML图:

  • 类复杂度分析:

 

 

  从图中可以看出,Main、Operator、Parse类的复杂程度过高,结构分工不明确。同时从整体架构来看,Parse类的结构设计并不精细,仅依靠函数调用表示层次关系,可扩展性很弱。Main类的Print函数完成了过多的任务,也不是一个明智的选择。部分代码之间的耦合度也比较高。 总体来说,第一次作业中,笔者的面向对象意识非常淡薄,更多的还是靠一个函数走天下(或者是好几个函数走天下)

hw2

由于第二次作业时笔者深深觉得这个架构过于混乱于是进行了重构,主要是对Parse类进行拆分,建立了明确的层次关系(Expr → Term → Factor);新建了容器类取代HashMap<指数,系数>

  • 解析:重构了架构,每一层均配有parse()函数,分割函数,向下传递函数,判断函数;新增三角因子和求和因子,将求和因子处理为若干个add操作
  • 输入处理:新增自定义函数代入过程,依靠有效逗号分割实参和形参,使用replace替换
  • Operator类:笔者认为拥有一个好的存储结果是非常必要的,于是建立了如下结构:
  • 输出:在第二次作业中,笔者取消了将print完全集成在main函数中的做法,而是在三角项类(cosine)、Tuple类、Operator类中分别写了简化函数和toString方法,以便最终输出
  • UML图
  • 类复杂度分析

     

     

  整体架构比上次作业要更面向对象一点,但是某些类的方法可以更精简,降低耦合度和复杂度。

  此外,对于容器选择,笔者在回看时发现这样的存储结果并不是一个明智的选择,可扩展性非常弱,一旦允许三角函数套娃,就无法避免重构的命运(事实证明果然如此)

hw3

第三次作业笔者主要更新了Operator类的系数容器,淘汰了不支持嵌套三角函数的cosine类(三角项)

  • 新的存储结构:

 

 

  选择使用不变的字符串列表存储连续相乘的若干个sin或cos因子,如此,在考虑三角函数嵌套时只需要传递内层三角函数的toString给外层的三角因子做内容即可

  • UML图

  • 类复杂度分析

  由于容器的改变,Input所要处理的过程变多了,因此复杂度有所提升

bug分析

  • 被hack
  1. 第二次测试在公测和互测中均被测出bug,原因分别为 ① 未考虑到自定义函数定义中含空白符的情况,导致解析出错 ② 由于在debug过程中测试到了sin运算时的问题并进行修复,而忘记了将相同的修复加到cos运算中导致cos运算对象不是x则产生报错
  2. 第三次测试在互测中被hack到了。原因是: 在化简过程中,对sin因子的判断不明确。一开始时sin因子的judge函数只应用于解析,此时被传入的字符串表达式不可能是sin()*sin(),所以judge函数只进行了简单的是否包含”sin” “cos”的判断,而在sin和cos的运算中,我设计的代码将根据sin()括号中的内容是否是简单的因子而判断是否加括号。但是可能出现sin(sin(x)*cos(x))的情况,导致少了一层括号。这个错误的根本原因在于前面写的局部应用的判断函数迁移到其他模块使用可能会有逻辑问题。
  • hack策略
  1. 笔者首先从一些比较常规的问题出发,测试基本功能,比如0, x , sin(-1) , sin(-x) ,sin(0),+-+1,带空格和制表符的表达式等
  2. 可以根据被测试者的代码进行有的放矢,比如被测试者进行了sin((-x))转化为-sin(x)的优化,则考虑负号是否被无脑提出,于是测试sin((-x))2;又比如被测者将指数1省略输出时,可以考虑x10是否为得到sin**0的情况
  3. 测试一些特殊的数据,如0,1,-1,+-+1,integer边界,以及sum替换i会误伤sin的情况
  4. 随机测试:利用python的库随机生成测试数据并验证正确性

心得体会

本次作业是我第一次进行面向对象编程,因此面向过程或是面向题目的特征还比较明显,架构也在一改再改。希望接下来的oo能在完成题目要求的同时写出更好的架构。


致谢: 感谢帮助我debug的每个人,新主楼的通宵教室,肯德基和便利蜂的24h营业

posted @ 2022-03-25 22:18  _反派甲  阅读(32)  评论(0编辑  收藏  举报