Unit1 总结
1. 程序结构分析
1.1 代码结构(类图)
1.2 类的度量统计
类名 | 属性个数 | 方法个数 | 方法名 | 方法规模(代码行) | 控制分支数目 | 类总代码规模(行) |
---|---|---|---|---|---|---|
Function | 4 | 6 | Function |
5 | 0 | 85 |
sort |
10 | 1 (双重循环) | ||||
getCan |
20 | 3 (条件+循环嵌套) | ||||
huanCan |
15 | 2 (条件替换逻辑) | ||||
prework |
15 | 2 (条件+循环) | ||||
transform |
30 | 5 (循环+递归解析) | ||||
Main | 0 | 1 | main |
10 | 0 | 10 |
ExpressionFactor | 2 | 2 | transform |
10 | 1 (条件) | 20 |
derive |
12 | 2 (循环+条件) | ||||
Dx | 0 | 2 | Dx |
1 | 0 | 25 |
transform |
20 | 4 (循环+嵌套解析) | ||||
Factor | 0 | 2 | transform (接口) |
0 | 0 | 4 |
derive (接口) |
0 | 0 | ||||
Functionhhh | 4 | 5 | Functionhhh |
5 | 0 | 60 |
getCan |
15 | 3 (条件+循环嵌套) | ||||
huanCan |
20 | 2 (参数替换逻辑) | ||||
prework |
10 | 1 (循环) | ||||
transform |
30 | 4 (循环嵌套替换) | ||||
Parser | 3 | 8 | Parser |
10 | 1 (符号处理) | 110 |
parseExpression |
20 | 2 (符号递归解析) | ||||
parseTerm |
15 | 3 (因子组合解析) | ||||
parseFactor |
40 | 6 (多重分支解析) | ||||
parseRectangle |
10 | 1 (条件解析) | ||||
parseExponent |
8 | 1 (条件) | ||||
parseNumber |
10 | 2 (符号+数字解析) | ||||
Poly | 1 | 9 | addTerm |
10 | 2 (Map合并逻辑) | 130 |
add |
15 | 2 (循环合并项) | ||||
sub |
15 | 2 (循环项取反) | ||||
mul |
30 | 3 (双重循环乘法) | ||||
merge |
10 | 2 (Map合并) | ||||
power |
25 | 4 (位运算+循环) | ||||
toString |
35 | 6 (多项条件格式化) | ||||
formatTerm |
30 | 5 (符号和幂次处理) | ||||
ConstantFactor | 1 | 2 | transform |
8 | 0 | 12 |
derive |
3 | 0 | ||||
Expression | 1 | 4 | derive |
15 | 2 (循环项派生) | 25 |
transform |
8 | 1 (循环合并项) | ||||
getTerms |
1 | 0 | ||||
Rectangle | 2 | 2 | toString |
10 | 1 (条件判断表达式复杂度) | 25 |
derive |
15 | 2 (type 类型判断+因子组合) |
||||
RectangleFactor | 2 | 4 | transform |
8 | 0 | 35 |
derive |
12 | 1 (递归调用条件) | ||||
same |
3 | 0 | ||||
merge |
2 | 0 | ||||
Term | 1 | 4 | addSign |
5 | 1 (sign 符号判断) |
30 |
transform |
10 | 1 (循环乘法操作) | ||||
derive |
15 | 2 (循环遍历因子+条件分支) | ||||
getFactors |
1 | 0 | ||||
VariableFactor | 1 | 2 | transform |
6 | 0 | 20 |
derive |
10 | 1 (exp == 0 条件判断) |
1.3 分析与自我评价
1.3.1 内聚性、耦合性分析
高内聚类
Poly
类- 优点:专注于多项式的存储和运算(加、减、乘、幂次、合并项等),所有方法围绕多项式操作展开,职责单一。
- 示例:
add()
、mul()
等方法仅处理多项式运算逻辑,无其他无关功能。
ConstantFactor
类- 优点:仅实现常数因子的转换和求导逻辑,代码简洁且高度聚焦。
VariableFactor
类- 优点:专注于变量因子(如
x^exp
)的转换和求导,逻辑清晰。
- 优点:专注于变量因子(如
低内聚类
Function
和Functionhhh
类- 问题:两者功能高度相似(处理函数替换和参数解析),但未通过继承或组合复用代码,导致职责分散。
- 示例:
getCan()
和huanCan()
方法在两个类中重复实现,违反单一职责原则。
Rectangle
类- 问题:同时承担几何表示(如
sin
/cos
)和代数运算的职责,混合了领域逻辑。 - 示例:
derive()
方法包含三角函数求导逻辑,与代数表达式解析无关。
- 问题:同时承担几何表示(如
低耦合模块
Factor
接口及其实现类- 优点:通过接口抽象,
Expression
和Parser
仅依赖Factor
接口,而非具体实现类,符合依赖倒置原则。
- 优点:通过接口抽象,
Parser
类- 优点:通过
parseFactor()
动态创建Factor
对象,减少对具体类的直接依赖。
- 优点:通过
高耦合模块
Function
与Functionhhh
类- 问题:两者直接依赖相同的字符串替换逻辑,且未通过抽象隔离,导致修改一处可能影响另一处。
Poly
与Term
类- 问题:
Poly
直接操作Term
的因子列表,未通过接口隔离,导致Poly
需要了解Term
的内部结构。
- 问题:
Rectangle
与ExpressionFactor
类- 问题:
Rectangle
的derive()
方法直接创建ExpressionFactor
对象,形成紧耦合。
- 问题:
1.3.2 代码优缺点总结
优点:
- 模块化设计
Factor
接口和Poly
类的设计体现了职责分离,便于扩展新因子类型或运算逻辑。
- 接口抽象
Factor
接口统一了因子转换和求导行为,支持多态调用。
- 递归解析能力
Parser
类的递归下降解析方法(如parseExpression()
)灵活处理复杂表达式。
缺点:
- 重复逻辑
Function
和Functionhhh
类中的getCan()
和huanCan()
方法重复实现。
- 紧耦合设计
Rectangle
类直接依赖ExpressionFactor
,导致几何逻辑与代数逻辑混杂。
- 冗余分支判断
Parser.parseFactor()
和Poly.formatTerm()
包含过多条件分支,增加维护成本。
- 缺乏封装
Term
类的factors
列表直接暴露给外部(如getFactors()
),违反封装原则。
1.3.3 改进方法
1. 内聚性优化
- 合并重复功能
- 将
Function
和Functionhhh
合并为FunctionHandler
类,通过策略模式区分不同参数替换逻辑。
- 将
- 拆分混合职责
- 将
Rectangle
的几何逻辑(如sin
/cos
)分离到TrigonometricFactor
类,保留Rectangle
仅表示代数变量。
- 将
2. 耦合性优化
- 依赖抽象而非实现
Poly
类应依赖Term
的接口(如TermInterface
),而非直接操作其内部factors
列表。Rectangle.derive()
应返回Factor
接口对象,而非具体ExpressionFactor
。
3. 代码复用与简化
- 提取工具类
- 将
Function
和Functionhhh
的公共逻辑(如字符串替换)封装到StringUtils
工具类。
- 将
- 使用模板方法模式
- 在
Parser
类中统一处理符号解析逻辑,减少parseFactor()
的条件分支。
- 在
4. 增强封装性
- 隐藏内部状态
- 移除
Term.getFactors()
方法,改为通过Term
内部方法操作因子列表。 - 将
Poly.terms
的访问权限设为private
,通过方法提供安全访问。
- 移除
实例:
// 改进1:合并 Function 和 Functionhhh
public class FunctionHandler {private FunctionType type; // 枚举区分不同函数类型public FunctionHandler(FunctionType type) { /* ... */ }public String transform(String input) { /* 根据类型调用不同替换逻辑 */ }
}// 改进2:拆分 Rectangle 的几何逻辑
public class TrigonometricFactor implements Factor {private String type; // "sin" 或 "cos"private Factor inner;@Overridepublic Factor derive() {// 实现三角函数求导逻辑,不依赖 ExpressionFator}
}
2. 架构设计体验
第一单元的作业针对“表达式展开”问题进行了三次开发。
第一次作业主要考察递归下降在解析表达式中的应用。由于不涉及复杂的运算,加上有先导课程的基础,本次作业比较容易完成。只要按照递归下降的方法逐层解析表达式、因子、项,并依次展开即可。
第二次作业加入了递推函数表达式和三角函数。对于递推函数,我在进行递归下降前添加了解析函数的部分,也就是先把所有 \(f\) 都替换成普通的表达式,然后再进入第一次作业的流程。这样只需要加入一个预处理步骤,不需要对第一次作业进行任何修改。对于三角函数,添加 \(\text{Rectangle}\) 类,包含函数类型及内部因子,其余操作和幂因子、表达式因子差不多。
第三次作业加入求导和普通函数。普通函数的部分很好处理。对于求导,再次添加一步预处理操作:将所有dx替换为普通表达式。在执行这一步时,为了方便且避免错误,我对dx内的表达式执行递归下降的解析并求导。需要注意的是,最好先预处理函数,再预处理dx。
在迭代开发、设计的过程中,我对这一问题的理解越来越深刻,对于java代码结构的掌控能力也一步步提升。
3. bug分析
第二次作业的提交在出现连续四个正负号时会出错。为了避免类似的情况,我增添了一步预处理:将所有连续的一段正负号缩写为一个,避免了不必要的麻烦。
第三次作业中,我在替换dx的时候没有添加括号,导致当dx的求导内容不止一项时运算顺序错误。
除此之外,替换函数内容需要获取实参和形参,在解析双参数的函数需要找到正确的分界 \(\text{','}\),可以利用括号匹配的方法判断。
4. 优化
在处理递推函数时,我先依次处理出了 \(f_1,f_2,f_3,f_4,f_5\) 的实际表达式,避免了每次都一步步递归造成的浪费。
在答案长度上并没有做太多优化,只是简单地合并同类项。在合并自定义的类时,判断是否相同需要自己编写相应的 \(\text{equal}\) 函数。
5. 心得体会
在复杂的、拓展的任务中,一个好的基本架构非常重要,它决定了未来代码编写的难度和准确度。因此在最初设计时,要充分考虑框架的适用性和可拓展性,当架构不足以满足后续的要求时要及时修改、重构。
进行迭代开发需要我们仔细思考如何加入新功能,最好能够保持原有部分不变,减少工作量和出错的概率。