第一单元的主要目标是了解面向对象的概念、认识对象的关键特性、了解并逐渐掌握层次化抽象和设计方法。单元内三次作业都围绕着函数求导这一主题,循序渐进地培养编写基本的面向对象程序的能力。
一、第一次作业:幂函数求导
第一次作业的要求是对仅包含常数和幂函数的多项式进行求导。自然的想法是,构造一个“项”对象,它包含系数和指数两个变量,多项式便是这样的项的集合。读入一个项后检查是否能与现有项合并化简,之后依次按公式求导,输出时优先输出系数为正的项。比较复杂的部分是输入环节,由于规则繁多,我总结出一个状态机并据此实现了InputReader类:
度量分析:
方法:
InputReader.InputReader() | 1.0 | 1.0 | 1.0 |
InputReader.isNumber(char) | 1.0 | 1.0 | 2.0 |
InputReader.isSign(char) | 1.0 | 1.0 | 2.0 |
InputReader.isSpace(char) | 1.0 | 1.0 | 2.0 |
InputReader.phase0(ArrayList) | 5.0 | 6.0 | 8.0 |
InputReader.phase1(ArrayList) | 5.0 | 5.0 | 8.0 |
InputReader.phase2(ArrayList) | 3.0 | 7.0 | 8.0 |
InputReader.phase3(ArrayList) | 2.0 | 3.0 | 5.0 |
InputReader.phase4(ArrayList) | 3.0 | 5.0 | 6.0 |
InputReader.phase5(ArrayList) | 4.0 | 4.0 | 6.0 |
InputReader.phase6(ArrayList) | 3.0 | 2.0 | 4.0 |
InputReader.phase7(ArrayList) | 2.0 | 6.0 | 7.0 |
InputReader.phase8(ArrayList) | 2.0 | 2.0 | 7.0 |
InputReader.readItems(ArrayList) | 5.0 | 6.0 | 7.0 |
Item.add(Item) | 1.0 | 1.0 | 1.0 |
Item.calcNewCoeff() | 1.0 | 1.0 | 1.0 |
Item.derive(boolean) | 1.0 | 9.0 | 9.0 |
Item.getCoefficient() | 1.0 | 1.0 | 1.0 |
Item.getIndex() | 1.0 | 1.0 | 1.0 |
Item.getNewCoefficient() | 1.0 | 1.0 | 1.0 |
Item.isAlive() | 1.0 | 1.0 | 1.0 |
Item.Item(String,String) | 1.0 | 1.0 | 1.0 |
PolynomialDerivation.combineItems() | 1.0 | 11.0 | 11.0 |
PolynomialDerivation.main(String[]) | 6.0 | 6.0 | 7.0 |
类:
InputReader | 4.0 | 56.0 |
Item | 1.875 | 15.0 |
PolynomialDerivation | 7.0 | 14.0 |
Total | 85.0 | |
Average | 3.5416666666666665 | 28.333333333333332 |
UML类图:
这次作业由于要求简单,实现的结构也较为简单,不需要继承、接口等。主要可优化的部分在于InputReader类,最好改成更加可读的实现方式。
BUG:
主要BUG来源于对输入数据规范的理解不清晰。例如在何种情况下允许正负号与系数之间出现空格,对空行和无输入的处理等。在编写过程中发现BUG的主要方法是根据自己认为的边界值及易错点构建测试样例,与同学交换测试样例大量测试。
二、第二次作业:三角函数以及函数相乘
第二次作业的规则引入了sin(x)和cos(x)函数,并且允许一项内出现同类或异类函数相乘。我第一次作业的设计不能表示这样的结构,需要在项(Item)之下引入新的因子(Factor)类。将InputReader中的部分状态机删除并简化为readFunction()、readItem()等函数。读入sin(x)和cos(x)时利用规则投机取巧采用了字符串间隔匹配,导致第三次作业该部分需要重写。有考虑到基于sin(x)^2+cos(x)^2=1的优化,但实现过程中出现了bug,最终舍弃。
度量分析:
方法:
Factor.add(Factor) | 1.0 | 1.0 | 1.0 |
Factor.derive() | 3.0 | 2.0 | 6.0 |
Factor.Factor(String,String) | 2.0 | 2.0 | 5.0 |
Factor.getIndex() | 1.0 | 1.0 | 1.0 |
Factor.getType() | 1.0 | 1.0 | 1.0 |
Factor.getValue() | 1.0 | 1.0 | 1.0 |
Factor.isConst() | 1.0 | 2.0 | 2.0 |
Factor.isNegativeOne() | 1.0 | 2.0 | 2.0 |
Factor.isOne() | 2.0 | 2.0 | 2.0 |
Factor.isPositive() | 1.0 | 1.0 | 1.0 |
Factor.isZero() | 1.0 | 2.0 | 2.0 |
Factor.setIndex(BigInteger) | 1.0 | 1.0 | 1.0 |
Factor.sub(Factor) | 1.0 | 1.0 | 1.0 |
Factor.times(Factor) | 1.0 | 2.0 | 2.0 |
Factor.toString() | 4.0 | 4.0 | 4.0 |
InputReader.filterSpaces() | 1.0 | 2.0 | 3.0 |
InputReader.InputReader() | 1.0 | 1.0 | 1.0 |
InputReader.intervalMatching(String) | 3.0 | 3.0 | 4.0 |
InputReader.isFunction(char) | 1.0 | 1.0 | 3.0 |
InputReader.isNumber(char) | 1.0 | 1.0 | 2.0 |
InputReader.isSign(char) | 1.0 | 1.0 | 2.0 |
InputReader.isSpace(char) | 1.0 | 1.0 | 2.0 |
InputReader.newItem(Polynomial) | 1.0 | 2.0 | 2.0 |
InputReader.phase0(Polynomial) | 4.0 | 6.0 | 6.0 |
InputReader.phase1(Polynomial) | 4.0 | 6.0 | 6.0 |
InputReader.phase2(Polynomial) | 3.0 | 4.0 | 4.0 |
InputReader.phase3(Polynomial) | 2.0 | 6.0 | 6.0 |
InputReader.phase4(Polynomial) | 4.0 | 5.0 | 5.0 |
InputReader.phase5(Polynomial) | 8.0 | 2.0 | 9.0 |
InputReader.readFunction(Polynomial,String) | 5.0 | 9.0 | 9.0 |
InputReader.readIndex() | 5.0 | 4.0 | 8.0 |
InputReader.readPolynomial(Polynomial) | 5.0 | 5.0 | 6.0 |
InputReader.seriesMatching(String) | 3.0 | 3.0 | 4.0 |
Item.add(Item) | 1.0 | 1.0 | 1.0 |
Item.combine(Item) | 2.0 | 2.0 | 3.0 |
Item.derive() | 2.0 | 6.0 | 7.0 |
Item.getFactors() | 1.0 | 1.0 | 1.0 |
Item.isPositive() | 1.0 | 1.0 | 1.0 |
Item.isSameType(Item) | 3.0 | 2.0 | 3.0 |
Item.isZero() | 1.0 | 1.0 | 1.0 |
Item.Item() | 1.0 | 1.0 | 1.0 |
Item.sub(Item) | 1.0 | 1.0 | 1.0 |
Item.times(Factor) | 1.0 | 1.0 | 1.0 |
Item.times(Item) | 1.0 | 2.0 | 2.0 |
Item.times(String) | 1.0 | 1.0 | 1.0 |
Item.toString() | 2.0 | 7.0 | 7.0 |
Polynomial.add(Item) | 3.0 | 5.0 | 5.0 |
Polynomial.add(Polynomial) | 1.0 | 2.0 | 2.0 |
Polynomial.combine() | 2.0 | 5.0 | 6.0 |
Polynomial.derive() | 1.0 | 2.0 | 2.0 |
Polynomial.getItems() | 1.0 | 1.0 | 1.0 |
Polynomial.isEmpty() | 1.0 | 1.0 | 1.0 |
Polynomial.Polynomial() | 1.0 | 1.0 | 1.0 |
Polynomial.toString() | 2.0 | 5.0 | 7.0 |
PolynomialDerivation.main(String[]) | 1.0 | 2.0 | 2.0 |
类:
Factor | 1.9333333333333333 | 29.0 |
InputReader | 3.388888888888889 | 61.0 |
Item | 2.076923076923077 | 27.0 |
Polynomial | 3.0 | 24.0 |
PolynomialDerivation | 2.0 | 2.0 |
Total | 143.0 | |
Average | 2.6 | 28.6 |
UML类图:
为方便合并同类项,对Factor以及Item类定义了基本运算,但其实add()与sub()是重复的。虽然没有继承自己定义的父类,但第一次重载了toString()方法,使得输出多项式更加简单,也更加便于输出前的检查。
BUG:
在实现cos(x)^2+sin(x)^2=1优化时出现了两个未能通过的中测数据点。后来经过分析,初步认定是化简后合并同类项时系数计算错误导致的。另一方面,加入因子类使得项的合并需要经过大量搜索,前期采用ArrayList存储因子会使得时间复杂度提升一个量级,因此改用HashMap实现。但这又是一个仅对第二次作业适用的手段(因为所有的sin和cos函数都相同)。
三、第三次作业:函数嵌套
第三次作业不再限制函数的自变量为x,这意味着三角函数和幂函数可以不断递归嵌套。我采用的方法是将幂函数、三角函数的自变量定义为多项式(多项式为项的和,项为系数和函数的积),再定义一个没有因子的x函数作为递归结构的终点。与递归构建多项式对应,在读入数据是采用了完全可解释的递归读入方法。第三次作业由于输入复杂,优化算法时间复杂度高风险高而收益低,因此只做了合并同类项程度的优化。
度量分析:
方法:
ConstFunc.ConstFunc(String) | 1.0 | 1.0 | 1.0 |
ConstFunc.derive() | 1.0 | 1.0 | 1.0 |
ConstFunc.getIndex() | 1.0 | 1.0 | 1.0 |
ConstFunc.getType() | 1.0 | 1.0 | 1.0 |
ConstFunc.getVar() | 1.0 | 1.0 | 1.0 |
ConstFunc.isSameTypeWith(Func) | 1.0 | 1.0 | 1.0 |
ConstFunc.isSameWith(Func) | 1.0 | 2.0 | 2.0 |
ConstFunc.times(Func) | 1.0 | 1.0 | 1.0 |
CosFunc.CosFunc(Polynomial,String) | 1.0 | 1.0 | 1.0 |
CosFunc.derive() | 1.0 | 2.0 | 2.0 |
CosFunc.getIndex() | 1.0 | 1.0 | 1.0 |
CosFunc.getType() | 1.0 | 1.0 | 1.0 |
CosFunc.getVar() | 1.0 | 1.0 | 1.0 |
CosFunc.isSameTypeWith(Func) | 1.0 | 2.0 | 2.0 |
CosFunc.isSameWith(Func) | 1.0 | 2.0 | 2.0 |
CosFunc.times(Func) | 1.0 | 1.0 | 1.0 |
CosFunc.toString() | 2.0 | 3.0 | 3.0 |
InputReader.atEnd() | 1.0 | 2.0 | 2.0 |
InputReader.filterSpaces() | 1.0 | 2.0 | 3.0 |
InputReader.InputReader() | 1.0 | 2.0 | 2.0 |
InputReader.isNumber(char) | 1.0 | 1.0 | 2.0 |
InputReader.isSign(char) | 1.0 | 1.0 | 2.0 |
InputReader.isSpace(char) | 1.0 | 1.0 | 2.0 |
InputReader.newConstPoly(String) | 1.0 | 1.0 | 1.0 |
InputReader.newFuncPoly(Func) | 2.0 | 1.0 | 2.0 |
InputReader.readConstFunc() | 2.0 | 1.0 | 2.0 |
InputReader.readCosFunc() | 4.0 | 1.0 | 4.0 |
InputReader.readFunc() | 7.0 | 7.0 | 8.0 |
InputReader.readIndex() | 4.0 | 3.0 | 5.0 |
InputReader.readInSin() | 9.0 | 9.0 | 12.0 |
InputReader.readItem() | 5.0 | 8.0 | 11.0 |
InputReader.readPolynomial(Polynomial,boolean) | 7.0 | 3.0 | 9.0 |
InputReader.readPowerFunc() | 2.0 | 1.0 | 2.0 |
InputReader.readSignedInt() | 5.0 | 4.0 | 8.0 |
InputReader.readSinFunc() | 4.0 | 1.0 | 4.0 |
InputReader.readXFunc() | 2.0 | 1.0 | 2.0 |
InputReader.seriesMatching(String) | 3.0 | 3.0 | 4.0 |
Item.add(Item) | 1.0 | 1.0 | 1.0 |
Item.derive() | 2.0 | 4.0 | 5.0 |
Item.filterOne() | 1.0 | 3.0 | 3.0 |
Item.getCoefficient() | 1.0 | 1.0 | 1.0 |
Item.getFuncArray() | 1.0 | 1.0 | 1.0 |
Item.isOne() | 1.0 | 2.0 | 2.0 |
Item.isPositive() | 1.0 | 1.0 | 1.0 |
Item.isSameTypeWith(Item) | 6.0 | 3.0 | 6.0 |
Item.isSameWith(Item) | 1.0 | 2.0 | 2.0 |
Item.isZero() | 4.0 | 3.0 | 5.0 |
Item.Item() | 1.0 | 1.0 | 1.0 |
Item.processFunc(int) | 2.0 | 3.0 | 3.0 |
Item.times(Func) | 5.0 | 6.0 | 7.0 |
Item.times(Item) | 1.0 | 2.0 | 2.0 |
Item.times(String) | 1.0 | 1.0 | 1.0 |
Item.toString() | 1.0 | 6.0 | 7.0 |
Polynomial.add(Item) | 3.0 | 4.0 | 4.0 |
Polynomial.add(Polynomial) | 1.0 | 2.0 | 2.0 |
Polynomial.clone() | 1.0 | 2.0 | 2.0 |
Polynomial.derive() | 1.0 | 2.0 | 2.0 |
Polynomial.filterZero() | 1.0 | 3.0 | 3.0 |
Polynomial.getItems() | 1.0 | 1.0 | 1.0 |
Polynomial.isOne() | 1.0 | 2.0 | 2.0 |
Polynomial.isSameWith(Polynomial) | 6.0 | 3.0 | 6.0 |
Polynomial.isZero() | 4.0 | 2.0 | 4.0 |
Polynomial.multiFactor() | 1.0 | 3.0 | 3.0 |
Polynomial.multiItem() | 1.0 | 1.0 | 1.0 |
Polynomial.Polynomial() | 1.0 | 1.0 | 1.0 |
Polynomial.times(Item) | 1.0 | 2.0 | 2.0 |
Polynomial.times(Polynomial) | 1.0 | 2.0 | 2.0 |
Polynomial.toString() | 2.0 | 5.0 | 7.0 |
PolynomialDerivation.main(String[]) | 1.0 | 2.0 | 2.0 |
PowerFunc.derive() | 1.0 | 2.0 | 2.0 |
PowerFunc.getIndex() | 1.0 | 1.0 | 1.0 |
PowerFunc.getType() | 1.0 | 1.0 | 1.0 |
PowerFunc.getVar() | 1.0 | 1.0 | 1.0 |
PowerFunc.isSameTypeWith(Func) | 1.0 | 2.0 | 2.0 |
PowerFunc.isSameWith(Func) | 1.0 | 2.0 | 2.0 |
PowerFunc.PowerFunc(Polynomial,String) | 1.0 | 1.0 | 1.0 |
PowerFunc.times(Func) | 1.0 | 1.0 | 1.0 |
PowerFunc.toString() | 1.0 | 2.0 | 2.0 |
SinFunc.derive() | 1.0 | 2.0 | 2.0 |
SinFunc.getIndex() | 1.0 | 1.0 | 1.0 |
SinFunc.getType() | 1.0 | 1.0 | 1.0 |
SinFunc.getVar() | 1.0 | 1.0 | 1.0 |
SinFunc.isSameTypeWith(Func) | 1.0 | 2.0 | 2.0 |
SinFunc.isSameWith(Func) | 1.0 | 2.0 | 2.0 |
SinFunc.SinFunc(Polynomial,String) | 1.0 | 1.0 | 1.0 |
SinFunc.times(Func) | 1.0 | 1.0 | 1.0 |
SinFunc.toString() | 2.0 | 3.0 | 3.0 |
XFunc.derive() | 1.0 | 2.0 | 2.0 |
XFunc.getIndex() | 1.0 | 1.0 | 1.0 |
XFunc.getType() | 1.0 | 1.0 | 1.0 |
XFunc.getVar() | 1.0 | 1.0 | 1.0 |
XFunc.isSameTypeWith(Func) | 1.0 | 1.0 | 1.0 |
XFunc.isSameWith(Func) | 1.0 | 2.0 | 2.0 |
XFunc.times(Func) | 1.0 | 1.0 | 1.0 |
XFunc.toString() | 2.0 | 2.0 | 2.0 |
XFunc.XFunc(String) | 1.0 | 1.0 | 1.0 |
类:
ConstFunc | 1.0 | 8.0 |
CosFunc | 1.3333333333333333 | 12.0 |
InputReader | 3.5 | 70.0 |
Item | 2.5625 | 41.0 |
Polynomial | 2.533333333333333 | 38.0 |
PolynomialDerivation | 2.0 | 2.0 |
PowerFunc | 1.2222222222222223 | 11.0 |
SinFunc | 1.3333333333333333 | 12.0 |
XFunc | 1.2222222222222223 | 11.0 |
Total | 205.0 | |
Average | 2.1354166666666665 | 22.77777777777778 |
UML类图:
定义了Func接口,由于前期对题目理解的问题,多定义了一个PowerFunc用于处理装载(x+2)^2的函数。由于实际不会出现,故其指数固定为1,用作多项式容器。常函数仅为便于读入而存在,因为对Item(项)类的定义中有单独的系数代表所有常数的乘积,而在读入时一并视为因子读入。这是设计得不够优美的地方。多项式类函数过多有些冗杂,其中有一些为优化而存在的函数最终并未调用。
BUG:
在输出时省略了部分括号导致出现BUG。比如cos(x+2)是非法输出,应为cos((x+2))。这与我在设计程序时将多项式直接作为三角函数的自变量不无关系,但可以通过在输出时添加一些判断解决。
四、Applying Creational Pattern
对于存储多项式、项和函数的类,都定义了基本运算、求导和输出函数,因此可看做使用了工厂模式。在前两次作业中尝试了简单工厂模式,第三次作业部分使用了抽象工厂模式。简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个程序结构的优化。但在类的数量增多时,难以理清大局。这三次作业是不断重构的过程,已经可以体现一部分对创建模式的改进。若对第三次作业进一步改进,可以考虑的方向有:拆分InputReader,将Function的加、乘方法写进父类等。