面向对象第一单元 总结

王宇龙-21373312 学生 2023-03-19 13:30:32

hw1

设计思路

架构

ExprNumVari 都实现 Factor 的接口,实现 simplify 方法, 利用 HashMap<SimpleExpr, Integer> 来维护最简形式的表达式,作为 $key$ 的 SimpleExpr 的属性为一个三元数组,为一个项 x , y , z 的指数组成的 3 元数对, $value$ 为该项系数., SimpleExpr 需要实现乘法功能(指数相加即可)。

同时Expr需要实现 toString 方法,这个方法根据 simplify 的结果输出。

img

Parser的实现

支持 parseExpr, parseTerm, parseFactor, parseVari, parseNum 各个方法即可。

对于空白符,不事先全部忽略,仅仅在需要处调用 lexerignorespace 方法,忽略接下来的若干个空格。这使得对于格式合法与否的判断更加严谨。如 x**+ 001 实际上不是一个合法的表达式,但是如果事先忽略所有空格则误判为合法。虽然本单元对于格式合法性的判断并没有要求,这样的处理方法似乎略显繁琐,实则不然,这样的设计提高了程序的健壮性,更能满足进一步开发的需求。

方法复杂度分析图表

Parser.parseVari()4.01.03.03.0
Parser.parseTerm()5.01.05.05.0
Parser.Parser(Lexer)0.01.01.01.0
Parser.parseNum()2.03.03.03.0
Parser.parseFactor()10.04.06.07.0
Parser.parseExpr()8.04.06.06.0
Main.main(String[])0.01.01.01.0
Lexer.next()1.02.01.02.0
Lexer.nEnd()0.01.01.01.0
Lexer.Lexer(String)0.01.01.01.0
Lexer.ignoreSpace()3.02.02.04.0
Lexer.getNum()3.01.03.04.0
Lexer.getIndex()3.03.02.04.0
Lexer.cur()1.01.01.02.0
expr.Vari.Vari(String, int)0.01.01.01.0
expr.Vari.simplify()0.01.01.01.0
expr.Term.Term()0.01.01.01.0
expr.Term.simplify()2.01.03.03.0
expr.Term.negate(HashMap)1.01.02.02.0
expr.Term.mult(HashMap, HashMap)3.01.03.03.0
expr.Term.clear(HashMap)4.01.04.04.0
expr.Term.addFactor(Factor, int)0.01.01.01.0
expr.SimpleExpr.tr(BigInteger)16.05.05.011.0
expr.SimpleExpr.SimpleExpr(int[])1.01.01.02.0
expr.SimpleExpr.SimpleExpr()0.01.01.01.0
expr.SimpleExpr.mul(SimpleExpr)1.01.01.02.0
expr.SimpleExpr.hashCode()1.02.01.02.0
expr.SimpleExpr.equals(Object)7.05.01.05.0
expr.Num.simplify()1.01.02.02.0
expr.Num.Num(BigInteger)0.01.01.01.0
expr.Expr.tString()13.04.06.08.0
expr.Expr.simplify()4.03.04.05.0
expr.Expr.setIndex(int)0.01.01.01.0
expr.Expr.mult(HashMap, HashMap)3.01.03.03.0
expr.Expr.Expr()0.01.01.01.0
expr.Expr.clear(HashMap)4.01.04.04.0
expr.Expr.addTerm(Term, int)0.01.01.01.0
expr.Expr.add(HashMap, HashMap, int)3.01.03.03.0

bug分析

对于化简结果为 0 的情况输出为空,需要进行特判。

hw2

本次加入了三角函数和自定义函数需求。

三角函数需求的解决

加入新类 Tri (三角函数), 实现 Factor 接口, 属性中包含了一个 Expr。

同时原来的 SimpleExpr 设计中仅仅只有三元数组,不能继续满足需求,所以加入两个 HashMap<Expr, Integer> 来支持 $\Pi \sin Expr\Pi\cos Expr$ 形式的存储,同样 $varlue$ 用来表示系数。

SimpleExpr 的比较也需要经历重写,不但比较三元数组,同时需要比较 Expr 和对应的 $value$ , 这需要递归实现。

自定义函数需求的解决

Parser 新增一个参数 HashMap<Character, String> arg , 建立自变量和参数因子字符串对应关系。解析表达式是,最外层即为自己对应自己,而遇到了函数调用,根据形式创建新的 Parser, 设定好 arg , 这个 Parser 会解析函数表达式,并在遇到变量因子时去解析对应的字符串,从而返回正确的结果。

这个设计其实并不健全,它本质上将函数调用处理和语义解析杂糅在了一起, 这中不健全的缺陷在hw3中完全暴露,最终导致了一次代码重构。

架构类图

img

方法复杂度

Parser.parseVari()6.02.04.04.0
Parser.parseTri()17.01.05.011.0
Parser.parseTerm()5.01.05.05.0
Parser.Parser(Lexer, ArrayList, ArrayList, HashMap)0.01.01.01.0
Parser.Parser(Lexer)1.01.02.02.0
Parser.parseNum()2.03.03.03.0
Parser.parseFuncDef()11.03.02.09.0
Parser.parseFuncCall()8.01.04.07.0
Parser.parseFactor()12.06.08.09.0
Parser.parseExpr()8.04.06.06.0
Parser.changeLexer(Lexer)0.01.01.01.0
Main.main(String[])1.01.02.02.0
Lexer.rest()0.01.01.01.0
Lexer.next()1.02.01.02.0
Lexer.nEnd()0.01.01.01.0
Lexer.Lexer(String)0.01.01.01.0
Lexer.ignoreSpace()3.02.02.04.0
Lexer.getNum()3.01.03.04.0
Lexer.getIndex()3.03.02.04.0
Lexer.cur()1.01.01.02.0
expr.Vari.Vari(String, int)0.01.01.01.0
expr.Vari.simplify()0.01.01.01.0
expr.Tri.Tri(int, int, Expr)0.01.01.01.0
expr.Tri.simplify()4.02.04.05.0
expr.Term.Term()0.01.01.01.0
expr.Term.simplify()3.02.03.04.0
expr.Term.negate(HashMap)1.01.02.02.0
expr.Term.mult(HashMap, HashMap)19.05.07.07.0
expr.Term.clear(HashMap)5.01.05.05.0
expr.Term.addFactor(Factor, int)0.01.01.01.0
expr.SimpleExpr.zero()0.01.01.01.0
expr.SimpleExpr.tr(BigInteger)26.05.011.017.0
expr.SimpleExpr.SimpleExpr(int[])1.01.01.02.0
expr.SimpleExpr.SimpleExpr()0.01.01.01.0
expr.SimpleExpr.mul(SimpleExpr)5.01.05.06.0
expr.SimpleExpr.getcoe()0.01.01.01.0
expr.SimpleExpr.cmp(SimpleExpr)32.015.09.017.0
expr.SimpleExpr.appendSin(Expr, int)12.05.04.09.0
expr.SimpleExpr.appendCos(Expr, int)6.05.03.06.0
expr.Num.simplify()1.01.02.02.0
expr.Num.Num(BigInteger)0.01.01.01.0
expr.Expr.tString()13.04.06.08.0
expr.Expr.simplify()5.04.04.06.0
expr.Expr.setIndex(int)0.01.01.01.0
expr.Expr.mult(HashMap, HashMap)19.05.07.07.0
expr.Expr.Expr()0.01.01.01.0
expr.Expr.cmp(Expr)22.09.05.012.0
expr.Expr.clear(HashMap)5.01.05.05.0
expr.Expr.addTerm(Term, int)0.01.01.01.0
expr.Expr.add(HashMap, HashMap, int)16.04.07.07.0

bug分析

在比较 SimpleExprExpr 的过程中,需要比较两个 HashMap (记为 map1map2 是否相同。笔者的实现时枚举 map1 中的 $key$ ,将其在两个 HashMap 中的 $value$ 进行比较。

然而这只能判断 map1 是否为 map2 的子集,需要额外对二者的 keySet 大小比较才能正确判断。

hw3

本次加入了求导的需求, 同时函数表达式可以包含之前定义过的表达式。

对有求导需求的 Expr , 先简化为 HashMap<SimpleExpr, Integer> 再求导,同样需要递归实现。

设计缺陷引发巨大灾难

新加入的求导功能似乎实现起来十分直观简明,只需为一些类添加求导方法即可。然而捋一捋求导,化简,调用函数三者的依赖关系,求导前要先化简,化简前要调用函数。那么,如果函数表达式中出现了求导运算会发生什么呢?糟糕!依赖关系成环了!

引发这场灾难的原因是前文提到的设计缺陷:函数调用处理和语义分析的界限被模糊了,原本两个可以更为独立的过程形成了一个微妙的“相互依赖”的关系,在hw2中,由于这种“相互依赖”是基于不同层次之间的,暂时没有让依赖图上产生环,但是它使得依赖关系变得臃肿而难以扩展,一旦一种跨层次的依赖被引入,架构便发生毁灭性的打击。

于是笔者重构了函数调用的实现。

重构后的函数调用

新增实现 Factor 接口的 Func 用于表示函数因子,属性包括表达式和参数。这样程序就可以无后顾之忧地先化简表达式,处理好其中的求导操作,然后再将参数代入了。

类图

img

方法复杂度

Parser.parseVari()4.01.03.03.0
Parser.parseTri()17.01.05.011.0
Parser.parseTerm()5.01.05.05.0
Parser.Parser(Lexer, ArrayList, ArrayList)0.01.01.01.0
Parser.Parser(Lexer)1.01.02.02.0
Parser.parseNum()2.03.03.03.0
Parser.parseFuncDef()11.03.02.09.0
Parser.parseFuncCall()8.01.04.07.0
Parser.parseFactor()13.07.09.010.0
Parser.parseExpr()8.04.06.06.0
Parser.parseDrExpr()2.01.01.03.0
Parser.changeLexer(Lexer)0.01.01.01.0
Main.main(String[])1.01.02.02.0
Lexer.rest()0.01.01.01.0
Lexer.next()1.02.01.02.0
Lexer.nEnd()0.01.01.01.0
Lexer.Lexer(String)0.01.01.01.0
Lexer.ignoreSpace()3.02.02.04.0
Lexer.getNum()3.01.03.04.0
Lexer.getIndex()3.03.02.04.0
Lexer.cur()1.01.01.02.0
expr.Vari.Vari(String, int)0.01.01.01.0
expr.Vari.simplify()0.01.01.01.0
expr.Tri.Tri(int, int, Expr)0.01.01.01.0
expr.Tri.simplify()4.02.04.05.0
expr.Term.Term()0.01.01.01.0
expr.Term.simplify()3.02.03.04.0
expr.Term.negate(HashMap)1.01.02.02.0
expr.Term.mult(HashMap, HashMap)19.05.07.07.0
expr.Term.clear(HashMap)5.01.05.05.0
expr.Term.addFactor(Factor, int)0.01.01.01.0
expr.SimpleExpr.zero()0.01.01.01.0
expr.SimpleExpr.tr(BigInteger)26.05.011.017.0
expr.SimpleExpr.substitute(HashMap)9.01.06.06.0
expr.SimpleExpr.SimpleExpr(int[])1.01.01.02.0
expr.SimpleExpr.SimpleExpr()0.01.01.01.0
expr.SimpleExpr.mult(HashMap, HashMap)19.05.07.07.0
expr.SimpleExpr.mul(SimpleExpr)5.01.05.06.0
expr.SimpleExpr.getcoe()0.01.01.01.0
expr.SimpleExpr.derive(int)10.02.06.07.0
expr.SimpleExpr.cmp(SimpleExpr)32.015.09.017.0
expr.SimpleExpr.clone()1.01.01.02.0
expr.SimpleExpr.appendSin(Expr, int)12.05.04.09.0
expr.SimpleExpr.appendCos(Expr, int)6.05.03.06.0
expr.SimpleExpr.add(HashMap, HashMap)13.04.06.06.0
expr.Num.simplify()1.01.02.02.0
expr.Num.Num(BigInteger)0.01.01.01.0
expr.Func.simplify()3.01.03.03.0
expr.Func.Func(Expr, HashMap)0.01.01.01.0
expr.Func.add(HashMap, HashMap)13.04.06.06.0
expr.Expr.tString()13.04.06.08.0
expr.Expr.substitute(HashMap)3.01.03.03.0
expr.Expr.simplify()5.04.04.06.0
expr.Expr.setIndex(int)0.01.01.01.0
expr.Expr.mult(HashMap, HashMap)19.05.07.07.0
expr.Expr.Expr(HashMap)0.01.01.01.0
expr.Expr.Expr()0.01.01.01.0
expr.Expr.derive(int)3.01.03.03.0
expr.Expr.cmp(Expr)22.09.05.012.0
expr.Expr.clear(HashMap)5.01.05.05.0
expr.Expr.addTerm(Term, int)0.01.01.01.0
expr.Expr.add(HashMap, HashMap, int)16.04.07.07.0
expr.DrExpr.simplify()0.01.01.01.0
expr.DrExpr.DrExpr(int, Expr)0.01.01.01.0

bug分析

如前文所述,新加入的求导操作可能引发依赖混乱,如果不充分测试,不使用包含求导的函数,可能无法发现依赖层面的bug。

...全文
24 回复 打赏 收藏 举报
写回复
回复
切换为时间正序
请发表友善的回复…
发表回复
发帖
2023年北航面向对象设计与构造

383

社区成员

2023年北京航空航天大学《面向对象设计与构造》课程博客
java 高校 北京·海淀区
社区管理员
  • 被Taylor淹没的一条鱼
  • 柠栀_Gin
加入社区
帖子事件
创建了帖子
2023-03-19 13:30
社区公告
暂无公告