一、 程序结构分析
第一次作业——多项式求导
第一次作业的目标比较简单,只有对幂函数的求导,因此主要的抽象出的数据类为项(Term)和多项式(Poly)。
1. 结构
涉及到核心功能的UML类图如下:
可以看到由于第一次作业的功能单一且个人对OO的概念理解很有限,抽象出的类比较少,主要有:
-
数据类
- Term:一个项,存储指数和系数
- Poly:一个多项式,用HashMap存储各项的指数和系数
数据类都继承自Expression抽象类,该抽象类实现了Derivable接口,通过derivative():Expression方法可以得到导函数
-
Parser:将字符串解析成Term和Poly
- TermParser:解析Term
- PolyParser:解析Poly
-
自定义异常:
- IllegalPolyException:要解析的Poly字符串不合法
- IllegalTermException:要解析的Term字符串不合法
(其实这个地方完全没有必要抽象出两个异常,一个WrongFormatException足够了。)
2. 复杂度分析(度量)
部分Metrics分析结果如下:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
task.one.math.Poly.add(BigInteger,BigInteger) | 2 | 1 | 2 |
task.one.math.Poly.add(Term) | 1 | 1 | 1 |
task.one.math.Poly.derivative() | 1 | 2 | 2 |
task.one.math.Poly.subtract(Term) | 1 | 1 | 1 |
task.one.math.Poly.toString() | 1 | 3 | 3 |
task.one.math.Term.Term(BigInteger,BigInteger) | 1 | 1 | 1 |
task.one.math.Term.compareTo(Term) | 1 | 1 | 1 |
task.one.math.Term.derivative() | 1 | 1 | 2 |
task.one.math.Term.getCoefficient() | 1 | 1 | 1 |
task.one.math.Term.getIndex() | 1 | 1 | 1 |
task.one.math.Term.toString() | 2 | 5 | 5 |
task.one.util.PolyParser.parsePoly(String) | 3 | 3 | 6 |
task.one.util.StrUtil.addHeadingPlusSign(String) | 2 | 2 | 3 |
task.one.util.StrUtil.defaultIfEmpty(String,String) | 2 | 1 | 2 |
task.one.util.StrUtil.defaultIfNull(String,String) | 2 | 1 | 2 |
task.one.util.StrUtil.trimHeadingPlusSign(String) | 2 | 3 | 3 |
task.one.util.StrUtil.zeroIfEmpty(String) | 1 | 1 | 1 |
task.one.util.TermParser.parseTerm(String) | 2 | 2 | 3 |
Class | OCavg | WMC |
---|---|---|
task.one.math.Poly | 1.8 | 9 |
task.one.math.Term | 1.83 | 11 |
task.one.util.PolyParser | 5 | 5 |
task.one.util.StrUtil | 1.8 | 9 |
task.one.util.TermParser | 3 | 3 |
可以看出,PolyParser由于功能复杂,分支较多,复杂度较高,OCavg=5,其余类的复杂度保持在平均水平。
第二次作业——增加sin(x)和cos(x)
这次作业不能再通过一个二元组(指数和系数)来表示每一个项了,不过sin(x)和cos(x)的变量固定为x,因此一个四元组表示每个项也是可以的。
从这里其实可以看出我个人还没有设计架构、为后续设计留出余量的思想和远见,因此程序的结构写的很死,局限性很强。这也直接导致了第三次作业的重构完全重写。
1. 结构
涉及到核心功能的UML类图如下:
与第一次作业最大的不同是,将所有的因子都继承自Factor抽象类,Term的multiply(Factor)方法传入的是一个抽象类。然而这个设计其实是一个表面功夫,multiply(Factor)方法使用一个多重分支语句判断Factor的具体类别,再根据不同的类别进行相应的操作,这样的结构其实是毫无扩展性可言的,如果添加了新的Factor,则不得不改动所有相关的代码。因此这次的思路虽然是面向对象的,但结构是很烂的,既没有扩展性,还把程序的每个方法写的很复杂,而当时编码的我并没有意识到这个问题。
2. 复杂度分析(度量)
部分Metrics分析结果如下:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
task.one.math.Constant.Constant(BigInteger) | 1 | 1 | 1 |
task.one.math.Constant.derivative() | 1 | 1 | 1 |
task.one.math.Constant.toTerm() | 1 | 1 | 1 |
task.one.math.Cos.Cos(BigInteger) | 1 | 1 | 1 |
task.one.math.Cos.derivative() | 2 | 1 | 2 |
task.one.math.Cos.getIndex() | 1 | 1 | 1 |
task.one.math.Cos.toString() | 3 | 2 | 3 |
task.one.math.Cos.toTerm() | 1 | 1 | 1 |
task.one.math.Expression.add(BigInteger,BigInteger,BigInteger,BigInteger) | 1 | 1 | 1 |
task.one.math.Expression.add(Expression) | 1 | 1 | 1 |
task.one.math.Expression.add(Term) | 1 | 1 | 1 |
task.one.math.Expression.derivative() | 1 | 1 | 1 |
task.one.math.Expression.getTerms() | 1 | 1 | 1 |
task.one.math.Expression.subtract(Term) | 1 | 1 | 1 |
task.one.math.Expression.toString() | 1 | 1 | 1 |
task.one.math.Power.Power(BigInteger) | 1 | 1 | 1 |
task.one.math.Power.derivative() | 2 | 1 | 2 |
task.one.math.Power.getIndex() | 1 | 1 | 1 |
task.one.math.Power.toString() | 4 | 3 | 4 |
task.one.math.Power.toTerm() | 1 | 1 | 1 |
task.one.math.Sin.Sin(BigInteger) | 1 | 1 | 1 |
task.one.math.Sin.derivative() | 2 | 1 | 2 |
task.one.math.Sin.getIndex() | 1 | 1 | 1 |
task.one.math.Sin.toString() | 3 | 2 | 3 |
task.one.math.Sin.toTerm() | 1 | 1 | 1 |
task.one.math.Term.Term(BigInteger,BigInteger,BigInteger,BigInteger) | 1 | 1 | 1 |
task.one.math.Term.Term(long,long,long,long) | 1 | 1 | 1 |
task.one.math.Term.compareTo(Term) | 1 | 1 | 1 |
task.one.math.Term.derivative() | 1 | 1 | 1 |
task.one.math.Term.getCoef() | 1 | 1 | 1 |
task.one.math.Term.getCosIndex() | 1 | 1 | 1 |
task.one.math.Term.getPwrIndex() | 1 | 1 | 1 |
task.one.math.Term.getSinIndex() | 1 | 1 | 1 |
task.one.math.Term.multiply(BigInteger) | 1 | 1 | 1 |
task.one.math.Term.multiply(Factor) | 1 | 1 | 1 |
task.one.math.Term.multiply(Term) | 1 | 1 | 1 |
task.one.math.Term.multiply(long) | 1 | 1 | 1 |
task.one.math.Term.negate() | 1 | 1 | 1 |
task.one.math.Term.toString() | 4 | 6 | 7 |
task.one.math.TermItem.TermItem(BigInteger,BigInteger,BigInteger) | 1 | 1 | 1 |
task.one.math.TermItem.equals(Object) | 3 | 4 | 6 |
task.one.math.TermItem.getCosIndex() | 1 | 1 | 1 |
task.one.math.TermItem.getPwrIndex() | 1 | 1 | 1 |
task.one.math.TermItem.getSinIndex() | 1 | 1 | 1 |
task.one.math.TermItem.hashCode() | 1 | 1 | 1 |
task.one.util.ExpressionParser.parseExpression(String) | 3 | 3 | 6 |
task.one.util.ExpressionParser.parseExpressionWithBlank(String) | 1 | 1 | 1 |
task.one.util.ExpressionParser.parseFactor(String) | 6 | 5 | 7 |
task.one.util.ExpressionParser.parseTerm(String) | 1 | 4 | 4 |
task.one.util.ExpressionSimplifier.ExpressionSimplifier(ArrayList |
1 | 1 | 1 |
task.one.util.ExpressionSimplifier.calcDiff(Term,Term,int,int) | 1 | 2 | 2 |
task.one.util.ExpressionSimplifier.compareResult() | 1 | 1 | 2 |
task.one.util.ExpressionSimplifier.dfs(Term,int,boolean,int) | 5 | 5 | 9 |
task.one.util.ExpressionSimplifier.getStartTime() | 1 | 1 | 1 |
task.one.util.ExpressionSimplifier.isType(Term,Term,int) | 6 | 5 | 6 |
task.one.util.ExpressionSimplifier.simplifiedTerms() | 1 | 2 | 3 |
task.one.util.ExpressionSimplifier.simplify(Expression) | 3 | 3 | 4 |
task.one.util.ExpressionSimplifier.transform(Term,Term,int,int) | 16 | 14 | 16 |
task.one.util.StrUtil.addHeadingPlusSign(Object) | 2 | 2 | 3 |
task.one.util.StrUtil.defaultIfEmpty(String,String) | 2 | 1 | 2 |
task.one.util.StrUtil.defaultIfNull(String,String) | 2 | 1 | 2 |
task.one.util.StrUtil.trimHeadingPlusSign(Object) | 2 | 3 | 3 |
task.one.util.StrUtil.zeroIfEmpty(String) | 1 | 1 | 1 |
Class | OCavg | WMC |
---|---|---|
task.one.math.Constant | 1 | 3 |
task.one.math.Cos | 1.6 | 8 |
task.one.math.Expression | 1 | 7 |
task.one.math.Power | 1.8 | 9 |
task.one.math.Sin | 1.6 | 8 |
task.one.math.Term | 1.43 | 20 |
task.one.math.TermItem | 1.33 | 8 |
task.one.util.ExpressionParser | 4.25 | 17 |
task.one.util.ExpressionSimplifier | 4.56 | 41 |
task.one.util.StrUtil | 1.8 | 9 |
由于这次涉及到了十分复杂的三角函数化简,因此ExpressionSimplifier的复杂度十分高,其中的transform(Term,Term,int,int)的各项复杂度都是正常方法的数倍。
而且,由于设计问题,Term承担了太多的任务,内部的方法十分复杂,因此Term类的许多方法的复杂度也很高。
ExpressionParser类由于解析字符串使用了正则表达式和许多分支语句、循环语句,复杂度也比较高。
第三次作业——增加嵌套因子和表达式因子
这次作业嵌套因子和表达式因子的加入,令我不得不一切从头开始,这是一个痛并快乐着的过程。
类的构建与作业指导书中的形式化描述十分接近。
其实这次的设计耦合度达到了前所未有的高度,这个问题在编写代码的过程中逐渐显现出来,并且当我意识到问题时已经没有时间打回重做了。同时,由于在完成作业时我对设计模式、结构等了解不够,使用了很多我自认为“面向对象”的、“低耦合”的写法,事后反思,这些写法都是导致程序扩展性差、耦合度高的罪魁祸首。
1. 结构
程序的类大致分为:
-
数据类:
- 表达式类:Expression
- 项类:Term
- 因子类
- 常数因子类ConstFactor:存储常数
- 幂函数因子类PowerFactor:存储x的指数(x为底)
- 三角函数因子抽象类
- 正弦函数类SinFactor:存储了括号内的因子和指数
- 余弦函数类CosFactor:结构和正弦函数类一致,只是具体实现有差异
- 表达式因子类ExprFactor:存储了一个内嵌的表达式
-
工具类
-
表达式树类ExprTree:其实是一个”字符串树“,每个树节点保存了一个字符串和一个子串的列表,字符串中使用串
"b<num>"
表示第<num>个子串出现的位置,以方便替换。为了方便在解析嵌套因子和嵌套表达式时获取子串,我直接在ExprTree类中实现了parseExpression, parseTerm和parseFactor方法,可以说耦合度十分高了。虽然ExprTree就是一个输入String,输出Expression的输入接口,但它的内部大量依赖于数据类中的各种类,没有扩展性。
-
表达式处理类ExpressionTransformer
- 表达式展开器类ExpressionExpander:将表达式中的括号展开
- 表达式合并器类ExpressionMerger:将表达式中的公因子提取合并
-
-
自定义异常,包括控制表达式化简时间的TLE异常和与具体实现相关的诸多异常。
2. 复杂度分析(度量)
部分Metrics分析结果如下:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
task.one.math.ConstFactor.ConstFactor(BigInteger) | 1 | 1 | 1 |
task.one.math.ConstFactor.ConstFactor(long) | 1 | 1 | 1 |
task.one.math.ConstFactor.compareTo(Factor) | 4 | 3 | 4 |
task.one.math.ConstFactor.diff() | 1 | 1 | 1 |
task.one.math.ConstFactor.getType() | 1 | 1 | 1 |
task.one.math.ConstFactor.getValue() | 1 | 1 | 1 |
task.one.math.ConstFactor.isNonZero() | 1 | 1 | 1 |
task.one.math.ConstFactor.simplify() | 1 | 1 | 1 |
task.one.math.ConstFactor.toString() | 1 | 1 | 1 |
task.one.math.ConstFactor.typeCompareTo(Factor) | 3 | 2 | 3 |
task.one.math.CosFactor.CosFactor(BigInteger) | 1 | 1 | 1 |
task.one.math.CosFactor.CosFactor(Factor,BigInteger) | 1 | 1 | 1 |
task.one.math.CosFactor.changeIndex(BigInteger) | 1 | 1 | 1 |
task.one.math.CosFactor.changeNestedFactor(Factor) | 1 | 1 | 1 |
task.one.math.CosFactor.compareTo(Factor) | 5 | 2 | 6 |
task.one.math.CosFactor.diff() | 2 | 1 | 2 |
task.one.math.CosFactor.getIndex() | 1 | 1 | 1 |
task.one.math.CosFactor.getNestedFactor() | 1 | 1 | 1 |
task.one.math.CosFactor.getType() | 1 | 1 | 1 |
task.one.math.CosFactor.isNonZero() | 1 | 1 | 1 |
task.one.math.CosFactor.simplify() | 2 | 2 | 3 |
task.one.math.CosFactor.toString() | 3 | 2 | 3 |
task.one.math.CosFactor.typeCompareTo(Factor) | 4 | 3 | 4 |
task.one.math.ExprFactor.ExprFactor(Expression) | 1 | 1 | 1 |
task.one.math.ExprFactor.compareTo(Factor) | 4 | 3 | 4 |
task.one.math.ExprFactor.diff() | 1 | 1 | 1 |
task.one.math.ExprFactor.getExpression() | 1 | 1 | 1 |
task.one.math.ExprFactor.getType() | 1 | 1 | 1 |
task.one.math.ExprFactor.isNonZero() | 1 | 1 | 1 |
task.one.math.ExprFactor.simplify() | 5 | 3 | 5 |
task.one.math.ExprFactor.toString() | 2 | 1 | 2 |
task.one.math.ExprFactor.typeCompareTo(Factor) | 1 | 1 | 1 |
task.one.math.Expression.Expression() | 1 | 1 | 1 |
task.one.math.Expression.Expression(ArrayList<Term>) | 1 | 1 | 1 |
task.one.math.Expression.add(Expression) | 1 | 2 | 2 |
task.one.math.Expression.add(Term) | 4 | 4 | 6 |
task.one.math.Expression.compareTo(Expression) | 4 | 3 | 4 |
task.one.math.Expression.diff() | 1 | 2 | 2 |
task.one.math.Expression.getTerms() | 1 | 1 | 1 |
task.one.math.Expression.isNonZero() | 3 | 2 | 3 |
task.one.math.Expression.multiply(Term) | 1 | 2 | 2 |
task.one.math.Expression.size() | 1 | 1 | 1 |
task.one.math.Expression.toString() | 2 | 1 | 2 |
task.one.math.PowerFactor.PowerFactor(BigInteger) | 1 | 1 | 1 |
task.one.math.PowerFactor.PowerFactor(long) | 1 | 1 | 1 |
task.one.math.PowerFactor.compareTo(Factor) | 5 | 3 | 5 |
task.one.math.PowerFactor.diff() | 2 | 1 | 2 |
task.one.math.PowerFactor.getIndex() | 1 | 1 | 1 |
task.one.math.PowerFactor.getType() | 1 | 1 | 1 |
task.one.math.PowerFactor.isNonZero() | 1 | 1 | 1 |
task.one.math.PowerFactor.simplify() | 2 | 1 | 2 |
task.one.math.PowerFactor.toString() | 3 | 2 | 3 |
task.one.math.PowerFactor.typeCompareTo(Factor) | 4 | 2 | 4 |
task.one.math.SinFactor.SinFactor(BigInteger) | 1 | 1 | 1 |
task.one.math.SinFactor.SinFactor(Factor,BigInteger) | 1 | 1 | 1 |
task.one.math.SinFactor.changeIndex(BigInteger) | 1 | 1 | 1 |
task.one.math.SinFactor.changeNestedFactor(Factor) | 1 | 1 | 1 |
task.one.math.SinFactor.compareTo(Factor) | 5 | 2 | 6 |
task.one.math.SinFactor.diff() | 2 | 1 | 2 |
task.one.math.SinFactor.getIndex() | 1 | 1 | 1 |
task.one.math.SinFactor.getNestedFactor() | 1 | 1 | 1 |
task.one.math.SinFactor.getType() | 1 | 1 | 1 |
task.one.math.SinFactor.isNonZero() | 1 | 2 | 2 |
task.one.math.SinFactor.simplify() | 3 | 1 | 3 |
task.one.math.SinFactor.toString() | 4 | 2 | 4 |
task.one.math.SinFactor.typeCompareTo(Factor) | 4 | 3 | 4 |
task.one.math.Term.Term(BigInteger,BigInteger) | 1 | 1 | 1 |
task.one.math.Term.Term(BigInteger,BigInteger,ArrayList<Factor>) | 1 | 1 | 1 |
task.one.math.Term.add(Term) | 1 | 1 | 1 |
task.one.math.Term.addFactor(ArrayList<Factor>,Factor) | 6 | 6 | 8 |
task.one.math.Term.checkAddable(Term) | 2 | 1 | 2 |
task.one.math.Term.compareTo(Term) | 5 | 2 | 5 |
task.one.math.Term.diff() | 4 | 4 | 5 |
task.one.math.Term.divide(Factor) | 10 | 11 | 20 |
task.one.math.Term.getFactors() | 1 | 3 | 3 |
task.one.math.Term.getOtherFactors() | 1 | 1 | 1 |
task.one.math.Term.isAddable(Term) | 5 | 2 | 5 |
task.one.math.Term.isNonZero() | 1 | 1 | 1 |
task.one.math.Term.multiply(BigInteger) | 1 | 1 | 1 |
task.one.math.Term.multiply(Factor) | 4 | 6 | 8 |
task.one.math.Term.multiply(Term) | 2 | 3 | 4 |
task.one.math.Term.multiply(long) | 1 | 1 | 1 |
task.one.math.Term.multiplyAll(Collection<Factor>) | 1 | 2 | 2 |
task.one.math.Term.negate() | 1 | 1 | 1 |
task.one.math.Term.toString() | 5 | 5 | 6 |
task.one.util.ExprTree.checkEmpty(String) | 2 | 1 | 2 |
task.one.util.ExprTree.checkFactor(String) | 2 | 1 | 2 |
task.one.util.ExprTree.checkRange(BigInteger) | 2 | 2 | 3 |
task.one.util.ExprTree.getAsFactor() | 1 | 1 | 1 |
task.one.util.ExprTree.getExpression() | 1 | 1 | 1 |
task.one.util.ExprTree.makeTree(String) | 2 | 1 | 2 |
task.one.util.ExprTree.parseExpression(String) | 2 | 3 | 5 |
task.one.util.ExprTree.parseFactor(String) | 2 | 4 | 6 |
task.one.util.ExprTree.parseTerm(String) | 2 | 3 | 5 |
task.one.util.ExprTree.parseTree(String) | 4 | 4 | 5 |
task.one.util.ExpressionExpander.ExpressionExpander(long,long) | 1 | 1 | 1 |
task.one.util.ExpressionExpander.checkTime() | 2 | 1 | 2 |
task.one.util.ExpressionExpander.expand(Expression) | 1 | 1 | 2 |
task.one.util.ExpressionExpander.expandAndChoose(Expression) | 2 | 1 | 2 |
task.one.util.ExpressionExpander.expandExprInTriInTerm(Term) | 1 | 4 | 4 |
task.one.util.ExpressionExpander.expandExprMultipliedInTerm(Term) | 4 | 3 | 4 |
task.one.util.ExpressionExpander.expandFromExpression(Expression) | 1 | 4 | 4 |
task.one.util.ExpressionExpander.transform(Expression) | 1 | 1 | 1 |
task.one.util.ExpressionMerger.ExpressionMerger(long,long) | 1 | 1 | 1 |
task.one.util.ExpressionMerger.checkTime() | 2 | 1 | 2 |
task.one.util.ExpressionMerger.merge(Expression) | 1 | 1 | 2 |
task.one.util.ExpressionMerger.mergeAndChoose(Expression) | 2 | 1 | 2 |
task.one.util.ExpressionMerger.mergeExpression(Expression) | 4 | 7 | 11 |
task.one.util.ExpressionMerger.mergeExpressionInTerm(Term) | 2 | 4 | 6 |
task.one.util.ExpressionMerger.mergeInnerExpression(Expression) | 1 | 3 | 3 |
task.one.util.ExpressionMerger.transform(Expression) | 1 | 1 | 1 |
task.one.util.StrUtil.addHeadingPlusSign(Object) | 2 | 2 | 3 |
task.one.util.StrUtil.defaultIfEmpty(String,String) | 2 | 1 | 2 |
task.one.util.StrUtil.defaultIfNull(String,String) | 2 | 1 | 2 |
task.one.util.StrUtil.trimHeadingPlusSign(Object) | 2 | 3 | 3 |
task.one.util.StrUtil.zeroIfEmpty(String) | 1 | 1 | 1 |
Class | OCavg | WMC |
---|---|---|
task.one.math.ConstFactor | 1.5 | 15 |
task.one.math.CosFactor | 1.92 | 25 |
task.one.math.ExprFactor | 1.89 | 17 |
task.one.math.Expression | 2.09 | 23 |
task.one.math.PowerFactor | 2.1 | 21 |
task.one.math.SinFactor | 2.08 | 27 |
task.one.math.Term | 3.58 | 68 |
task.one.util.ExprTree | 3.1 | 31 |
task.one.util.ExpressionExpander | 2.12 | 17 |
task.one.util.ExpressionMerger | 3 | 24 |
task.one.util.StrUtil | 1.8 | 9 |
重点关注的是
- Term的divide(Factor)方法,其实这个方法仅在化简过程中提取公因子时使用了,是否应该将这个复杂度爆炸的方法归入Term我个人也很不确定。
- ExprTree类由于功能复杂,复杂度很高。
程序结构分析部分小结
三次作业的迭代过程对我学习和理解面向对象的思想起了至关重要的作用。三次作业,基本上是写了三个互不相关的版本,由于每一次的架构设计都十分狭小、死板,因此每一次的迭代都意味着一次重新开始。在三次作业之后,慢慢地对面向对象的思想有了一些自己的理解, 但从”面向对象“来看,三次作业都是很”面向对象“的,但是从设计架构来看,三次作业的架构都不是很好,出现了冗长的类、冗长的方法,过度的相互依赖。在下一个单元的作业中这些都应该尽量避免。
学习好的设计模式对面向对象的设计是很有帮助的,在完成博客作业的过程中,我阅读了《设计模式——可复用面向对象软件基础》这本书的几个章节,对自己作业中程序结构的认识也更加全面、客观。如果时间允许,我希望能够运用更多好的设计思路重写这个单元的程序。
二、 BUG分析
在本单元的三次作业中,我的程序成功通过了所有的公测和互测,均没有发现BUG。单纯从正确性角度来看,本单元的作业并没有太多难以实现的部分,主要的难度还是在怎么设计一个易于扩展的,结构简单清晰的架构。
三、 测试策略
互测是OO课程的特色,也是我们互相学习研究架构设计、帮助伙伴完善程序功能的宝贵机会。个人在第一单元对别人程序的测试主要分为黑盒测试和白盒测试两部分。由于时间精力有限,以黑盒测试为主。
通过搭建自动化测试程序(评测机),对所有互测代码进行黑盒测试。测试的数据主要分为完全随机和覆盖性测试两个部分,其中完全随机的测试数据是通过计算机随机生成的,而覆盖性测试由于个人能力有限未能实现自动生成,而是手动构建的。
与黑盒测试同步进行的是代码阅读,然而这个阶段的代码阅读以学习和借鉴思路和架构为主,没有仔细检查代码实现细节的正确性,因此,白盒测试并不是从代码阅读直接开始的,而是在黑盒测试报告存在BUG之后,通过缩小测试数据的范围来定位BUG,在找到BUG所在的模块后才开始的。
四、 应用对象创建模式来重构
个人在本单元的三次作业中均没有使用任何的对象创建模式,这是这次作业的一个遗憾。从实验课中我们学习到了工厂模式、抽象工厂模式,此外在参考书籍中我还了解到了其他的一些对象创建模式如builder、单例模式等。
对于第一次和第二次作业,其实由于本身架构的灵活度不高(二元组、四元组),对象创建模式的意义不是很大。
而对于第三次作业,最适合使用的应该就是工厂方法模式了。由于本单元中的因子(Factor)种类众多,针对我个人的作业代码,就出现了5种具体实现了的类,因此使用工厂方法模式可以将对象创建这一过程很好地封装起来,在日后扩展时也更方便。
事实上,对于我的作业来说,在ExprTree类中已经实现了类似的功能,ExprTree中的parseFactor(String):Factor方法就是输入字符串,解析成一个因子。之所以没有将其单拎出来成为一个工厂类,是为了图写代码一时方便,想直接通过变量访问表达式树中子树的表达式。事后看来,这完全是牺牲了程序结构的不划算的行为。
此外,由于使用了正则表达式的捕获功能,将不同类型因子的解析写在一起也是对程序运行效率的考虑。如果将不同类型因子的解析分离,则需要先匹配再将解析任务分派给不同的方法(或类),将匹配和捕获同步进行的过程拆解成了先匹配,后解析的两个分开的过程,性能有所损失。然而,从大局来看,这点损失绝大多数情况下是完全可以接受的,相反这种杂糅的写法对于程序结构的影响是灾难性的。
五、 对比和心得体会
1. 对比:
参考代码和四份优秀代码的大体思路还是分为两种,这也是我在和同学交流的时候了解到的大家使用量两种思路:一个是将组合分成”加法序列“和”乘法序列“,即有类来专门存储a+b+c+d
和a*b*c*d
这样的形式;另一个思路是每一个组合都仅涉及两个元素,a+b
、a*b
这样,然后嵌套起来形成类似于二叉树的结构,这也是课程组推荐的一种写法。
个人认为两种写法没有哪一种是更有优势。可能仅涉及两个项的组合代码更加具有结构性,层次更加分明;但序列的写法更加接近于现实世界中表达式的模型,理解起来更加容易一点。
与这几份样例代码相比,我的程序主要的不足之处在于:
- 高耦合度:样例代码在一些模块之间的耦合度明显要低于我的程序:比如我的程序将字符串的解析、提取和表达式的构建、格式检查全部糅合在一起,没有很好的模块化,有悖于面向对象、模块化设计的思想。
- 一些类的职责过重:我的一些类存在着担负过多职责的问题,比如Term类,写了二三十个方法,代码数百行,而方法只是看似属于Term本身,其实完全是其他类的处理过程中涉及到的,与类本身并没有太大的关系,这也导致了代码读起来比较费劲,整体结构有些失衡。
- 过多的控制语句:受面向过程思想的影响,在编码的过程中还是倾向于在一个方法内通过尽可能复杂的控制流程完成一个尽可能复杂的过程,而不是将其职责细分到不同的方法和类中。
2. 心得体会
- 面向对象的学习绝不只是理论的学习,思维方式的培养只有在实战中进行,三次作业的迭代开发对面向对象思维的构建是不可少的。
- 面向对象很重要的一点在于抽象,将实体通过一定的方法构建为一个好的、恰当的数据抽象需要丰富的理论和设计经验。但是想要程序的扩展性、层次结构更上一层楼,学习前辈们总结出的好的设计模式是不可少的。好的设计模式充分体现了面向对象程序设计可重用、模块化的优点,能够帮助我们做出更好的设计,写出更好的OO代码。
- 随着程序规模的扩大,“干就完了”已经行不通了。好的设计才是程序的灵魂,编码只是实现设计的最后一步,写出功能完备、健壮性强的代码固然是必备素养,但没有合理、完善的设计,编码的过程只会越来越痛苦。OO课程学习的是科学的设计方法,而不仅仅是如何写代码。
再接再厉。