302
社区成员
发帖
与我相关
我的任务
分享| 类名 | 属性个数 | 类名 | 属性个数 |
|---|---|---|---|
| Node | 0 | 1 | 3 |
| Factor | 0 | 0 | 2 |
| Constant | 1 | 2 | 8 |
| Variable | 1 | 2 | 8 |
| ExprFactor | 2 | 2 | 10 |
| Expr | 2 | 2 | 18 |
| Term | 2 | 2 | 14 |
| Sign | 0 | 0 | 3 |
| Parser | 2 | 1 | 110 |
| Poly | 1 | 12 | 95 |
| MainClass | 0 | 1 | 7 |
Node
Constant
Constant(BigInteger):1行,无分支。
toPoly():1行,无分支。
Variable
Variable(int):1行,无分支。
toPoly():1行,无分支。
ExprFactor
ExprFactor(Expr, int):1行,无分支。
toPoly():2行,无分支。
Expr
Expr(List, List):1行,无分支。
toPoly():10行,包含一个循环(分支数=1)。
Term
Term(Sign, List):1行,无分支。
toPoly():7行,包含一个循环和一次条件判断(分支数=2)。
Parser
peek():3行,1个条件分支。
next():1行,无分支。
match(char):2行,1个条件分支。
expect(char):2行,1个条件分支。
isDigit(char):1行,无分支。
parseUnsignedInteger():6行,1个循环。
parseSignedInteger():7行,1个条件分支。
parseExponent():4行,1个条件分支。
parseExpr():12行,2个条件分支。
parseTerm():9行,1个条件分支,1个循环。
parseFactor():20行,嵌套条件分支(分支数≈4)。
Poly
私有构造 Poly():2行,无分支。
私有构造 Poly(TreeMap):3行,无分支。
constant(BigInteger):5行,1个条件分支。
variable(int):4行,1个条件分支。
add(Poly):8行,1个循环,无分支。
subtract(Poly):1行,无分支。
multiply(Poly):8行,2层循环,无分支。
pow(int):8行,1个循环和条件分支。
negate():4行,1个循环。
normalize():1行,无分支。
equals(Object):4行,2个条件分支。
toString():28行,多个条件分支和循环(分支数≈8)。
MainClass
| 度量指标 | 值 | 说明 |
|---|---|---|
| DIT(继承深度) | 最大 2 | Factor 继承 Node,Constant、Variable、ExprFactor 继承 Factor,Expr 继承 Node,Term 继承 Node。 |
| NOC(子类数量) | Node 有 4 个子类 Factor 有 3 个子类 | 继承结构清晰,扩展性一般。 |
| CBO(耦合对象数) | 较高 | Parser 依赖 Expr、Term、Factor、Constant、Variable、ExprFactor、Sign、Poly;Poly 依赖 BigInteger、TreeMap;Expr 依赖 Term、Sign;Term 依赖 Factor;MainClass 依赖 Parser、Expr、Poly。 |
| LCOM(内聚缺乏度) | 中等 | 每个类的方法基本都使用了类属性,Parser 的多个方法共享状态,内聚较好;Poly 的方法均围绕 coeff 操作,内聚高。 |
| RFC(响应集) | 较大 | Poly 类方法数较多,且调用 BigInteger、TreeMap,RFC 较大但合理。 |
Poly:所有方法均围绕 coeff 映射进行多项式运算,内聚度高。
Parser:所有方法均用于解析输入字符串,共享 pos 状态,内聚性好。
Constant、Variable、ExprFactor:每个类只负责一种因子,职责单一,内聚高。
Expr、Term:负责组合因子并转换,内聚良好。
Node 抽象层:定义了统一的转换接口,为多态提供基础。
Parser 与具体 Factor 子类耦合:在 parseFactor() 中直接 new 出 Constant、Variable、ExprFactor,耦合较紧。若增加新因子类型,需修改 Parser。
Poly 与 BigInteger、TreeMap:使用标准库,耦合度低。
Expr、Term、Factor:通过 toPoly() 返回 Poly,实现了解耦,但 Poly 作为统一表示,所有节点都依赖它,属于依赖倒置。
MainClass 只依赖 Parser 和 Poly,耦合度较低。
┌─────────────────┐
│ Node │
│ ─────────────── │
│ + toPoly(): Poly│
└────────┬────────┘
│
┌────┴────┬─────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ Factor │ │ Expr │ │ Term │
│ (抽象) │ │ │ │ │
└────┬────┘ └─────────┘ └─────────────┘
│
┌───┴────────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────────┐
│Constant│ │Variable│ │ ExprFactor │
└────────┘ └────────┘ └────────────┘
┌────────────┐
│ Poly │
│ (值对象) │
└────────────┘
┌──────────┐
│ Parser │ —— 解析并创建 Expr、Term、Factor 对象
└──────────┘
┌──────────┐
│ MainClass│
└──────────┘
关联关系:
- Parser 使用 Expr、Term、Factor 及其子类
- Expr 包含 Term 列表和 Sign 列表
- Term 包含 Factor 列表
- 所有 Node 子类都产生 Poly 对象
- Poly 使用 BigInteger、TreeMap
Node:定义统一转换接口,为表达式树提供多态基础。
Factor:抽象因子,为常量、变量、表达式因子提供共同父类,便于 Term 统一管理。
Constant / Variable / ExprFactor:分别封装不同类型的因子,每个类职责单一,便于扩展。
Expr:表示表达式,由若干带符号的项组成,toPoly() 将各项合并。
Term:表示项,由若干因子相乘而成,支持符号。
Sign:枚举符号,增强代码可读性。
Parser:递归下降解析器,按文法解析表达式、项、因子,职责单一。
Poly:多项式表示,支持加法、乘法、幂运算、输出等,作为最终计算结果。
MainClass:入口类,负责读入、解析、输出。
利用随机表达式生成器,对比手工计算结果与程序输出,验证正确性。
| 迭代点 | 说明 |
|---|---|
| 新增指数函数因子 ExpFactor | 支持 exp(...) 函数,可带指数 |
| 新增自定义函数因子 FuncFactor | 支持函数定义 f(x)=... 和调用 f(...) |
| 新增选择器因子 SelectorFactor | 支持条件选择 [ (a==b) ? c : d ] |
| 扩展 Parser | 增加对 exp、f、[ 的解析 |
| 统一输出接口 | Node 增加 toCanonicalString(),各节点实现标准化输出 |
| 引入 Expr 与 Term 的标准化输出 | 支持合并同类项(含 exp 合并) |
| 增加 FunctionDef 类 | 存储函数定义表达式 |
| MainClass 支持读取函数定义 | 第一行输入为函数定义个数(n),若 n==1 则读取函数定义 |
| 类名 | 属性个数 | 类名 | 属性个数 |
|---|---|---|---|
| Node | 0 | 2 | 4 |
| Factor | 0 | 2 | 3 |
| Constant | 1 | 5 | 22 |
| Variable | 1 | 5 | 20 |
| ExprFactor | 2 | 5 | 27 |
| ExpFactor | 2 | 6 | 50 |
| FuncFactor | 2 | 10 | 90 |
| SelectorFactor | 4 | 8 | 110 |
| Expr | 2 | 6 | 80 |
| Term | 2 | 8 | 120 |
| Sign | 0 | 0 | 3 |
| Parser | 2 | 14 | 170 |
| Poly | 1 | 12 | 95 |
| FunctionDef | 1 | 2 | 8 |
| MainClass | 1 | 3 | 35 |
Node
toPoly():抽象。
toCanonicalString():抽象
Factor
Constant
Constant(BigInteger):1行,无分支。
toPoly():1行,无分支。
Variable
Variable(int):1行,无分支。
toPoly():1行,无分支。
ExpFactor
toCanonicalString():约20行,含分支(指数0、参数0、参数是否为表达式因子等)
toPoly():抛异常(不支持转换为多项式),含分支
FuncFactor
toCanonicalString():调用替换后表达式
substitute 系列方法:递归替换,分支多(根据因子类型),约40行,分支数≈5
toPoly():抛异常
SelectorFactor
toCanonicalString() 和 toPoly():都尝试比较 a 和 b,若相等返回 c 否则 d,包含 try-catch 和 normalize 辅助方法
normalize 方法:约30行,分支数≈4(根据因子类型)
整体复杂,分支数较多
Expr
Term
toCanonicalString():约70行,包含 exp 因子合并逻辑,分支数≈5
getMergedNonConstFactors():约40行,分支数≈4
Parser
新增 parseExpFactor()、parseFuncFactor()、parseSelectorFactor()
parseFactor() 分支增加,总分支数≈6
| 度量指标 | 值 | 说明 |
|---|---|---|
| DIT(继承深度) | 最大 2 | 与作业一相同 |
| NOC(子类数量) | Node 有 5 个子类(Factor、Expr、Term)Factor有 6 个子类(Constant、Variable、ExprFactor、ExpFactor、FuncFactor、SelectorFactor) | 新增三个因子类 |
| CBO(耦合对象数) | 较高 | Parser 依赖所有因子类、Expr、Term、Sign、Poly;Expr、Term 互相依赖;FuncFactor 依赖 Expr、Term、Factor 子类;SelectorFactor 依赖 Poly 及其他因子;MainClass 依赖 Parser、FunctionDef。耦合度显著增加。 |
| LCOM(内聚缺乏度) | 中等 | 新增类(如 FuncFactor)方法多且均围绕替换逻辑,内聚较好;但 Expr 的 toCanonicalString() 同时处理多项式转换和手动合并,职责稍多。 |
| RFC(响应集) | 较大 | 特别是 Expr、Term、SelectorFactor 等类响应集较大。 |
ExpFactor:专注于 exp 函数的表示和标准化输出,内聚良好。
FuncFactor:专注于函数调用替换,所有方法围绕替换展开,内聚高。
SelectorFactor:专注于条件选择,内聚较高,但 normalize 方法涉及多种因子标准化,稍显复杂。
Expr / Term:toCanonicalString() 包含了合并同类项、合并 exp 因子的逻辑,职责过重,内聚性有所下降。
Parser 与所有因子类:parseFactor() 中直接 new 各因子类,耦合紧,违反开闭原则。
FuncFactor 与 MainClass:通过 MainClass.getFunctionDef() 获取定义,形成全局依赖,耦合较紧。
SelectorFactor 与 Poly:尝试用 Poly.equals() 比较,但 ExpFactor 等无法转为 Poly,因此增加 normalize 回退,耦合复杂。
Expr / Term 与 Poly:toCanonicalString() 中尝试使用 toPoly(),若失败则手动合并,两种路径耦合。
┌─────────────────┐
│ Node │
│ ─────────────── │
│ + toPoly(): Poly│
│ + toCanonicalString(): String
└────────┬────────┘
│
┌────┴────┬──────────────────────┬───────────────────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────────┐
│ Factor │ │ Expr │ │ Term │ │ Poly │
│ (抽象) │ │ │ │ │ │ (值对象) │
└────┬────┘ └─────────┘ └─────────────┘ └─────────────┘
│
┌───┴────────────────────────────────────────────────────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐
│Constant│ │Variable│ │ ExprFactor │ │ ExpFactor│ │FuncFactor│ │SelectorFact│
└────────┘ └────────┘ └────────────┘ └──────────┘ └──────────┘ └────────────┘
┌──────────────┐
│ FunctionDef │
└──────────────┘
┌──────────┐
│ Parser │
└──────────┘
┌──────────┐
│ MainClass│
└──────────┘
关联关系:
- Parser 依赖所有 Node 子类、Sign、Poly
- Expr 包含 Term 和 Sign
- Term 包含 Factor
- FuncFactor 依赖 Expr、Term、Factor 子类
- SelectorFactor 依赖 Factor 子类、Poly
- MainClass 持有 FunctionDef 并传递给 Parser
Node:增加了 toCanonicalString(),用于标准化输出,统一接口。
Factor:增加 toExpr(),便于将因子转为表达式,用于替换等场景。
Constant / Variable:简单实现新增方法。
ExprFactor:保留原有功能,新增 toCanonicalString() 尝试展开或保留括号。
ExpFactor:表示指数函数,toCanonicalString() 处理 exp 合并规则。
FuncFactor:表示函数调用,在标准化时进行参数替换。
SelectorFactor:表示条件选择,根据条件选择分支。
Expr:toCanonicalString() 尝试先转多项式,若失败则手动合并同类项。
Term:toCanonicalString() 增加了 exp 因子的合并逻辑(如 exp(a)*exp(b) → exp(a+b))。
Parser:增加对新因子的解析,文法扩展。
Poly:未变。
FunctionDef:简单封装函数定义表达式。
MainClass:增加读取函数定义的逻辑。
单元测试:对新增的 ExpFactor、FuncFactor、SelectorFactor 单独测试标准化输出。
随机生成测试:随机生成合法表达式,对比手工推导结果。
| 迭代点 | 说明 |
|---|---|
| 新增求导功能 | 引入 DerivativeFactor,支持 dx(...)、dy(...)、grad(...) 算子,并对所有节点实现 derive(String var) 方法 |
| 支持二元多项式 | 变量从单一 x 扩展为 x 和 y,Poly 改用 Monomial(单项式)表示,支持多元项 |
| 递推函数定义 | 引入 RecursiveCallFactor 和 FunctionManager,支持递推函数定义(如 f{0}(x)=...,f{1}(x)=...,f{n}(x)=...) |
| 函数合法性校验 | 新增 Validator 类,检查函数定义中是否包含不允许的语法(如求导、选择器、变量 y 等) |
| 求导规则完善 | 为所有因子实现求导规则:常数、变量、exp、expr、函数调用、选择器、递推调用等 |
| 表达式标准化增强 | 支持合并同类项(含 exp 合并) |
| 类名 | 属性个数 | 类名 | 属性个数 |
|---|---|---|---|
| Node | 0 | 3 | 5 |
| Factor | 0 | 2 | 4 |
| Constant | 1 | 7 | 28 |
| Variable | 2 | 9 | 48 |
| ExprFactor | 2 | 8 | 50 |
| ExpFactor | 2 | 8 | 70 |
| FuncFactor | 4 | 11 | 100 |
| RecursiveCallFactor | 2 | 6 | 30 |
| SelectorFactor | 4 | 8 | 110 |
| DerivativeFactor | 2 | 6 | 55 |
| Expr | 2 | 7 | 95 |
| Term | 2 | 10 | 140 |
| Sign | 0 | 0 | 3 |
| Parser | 2 | 18 | 210 |
| Poly | 1 | 12 | 110 |
| FunctionManager | 5 | 12 | 100 |
| Monomial | 2 | 8 | 35 |
| Validator | 0 | 6 | 60 |
| MainClass | 0 | 3 | 45 |
DerivativeFactor
RecursiveCallFactor
FunctionManager
parseRecursiveDefinition:约 25 行,分支(是否为模板 f{n} 或数字索引)。
expandRecursiveCall:约 45 行,含多个条件分支(索引是否为 -1/-2、是否缓存等),分支数≈4。
expandAllRecursiveCalls 及辅助方法:递归展开,分支较多。
Monomial
Validator
Poly 修改
Parser 修改
新增 parseDerivativeFactor 方法(约 15 行,分支按算子选择)。
parseFuncFactor 扩展支持 f{...} 形式的递推调用,增加了对索引的解析(约 20 行,分支)。
parseFactor 中增加对 dx、dy、grad 的识别,分支数增加。
Node 及各个子类
所有节点增加 derive(String var) 方法实现,各方法规模如下:
| 度量指标 | 值 | 说明 |
|---|---|---|
| DIT(继承深度) | 最大 2 | 与作业一二相同 |
| NOC(子类数量) | Node 有 5 个子类(Factor、Expr、Term)Factor有 7 个子类(Constant、Variable、ExprFactor、ExpFactor、FuncFactor、SelectorFactor、RecursiveCallFactor、DerivativeFactor) | 新增两个因子类 |
| CBO(耦合对象数) | 较高 | 新增 FunctionManager 作为全局管理器,被 Parser、RecursiveCallFactor、Validator 等多处依赖;Poly 与 Monomial 紧密耦合;Validator 依赖所有节点类。整体耦合度显著增加。 |
| LCOM(内聚缺乏度) | 中等 | FunctionManager 方法众多且都围绕递推展开,内聚较好;但 Parser 职责过重(解析+构建),内聚下降。 |
| RFC(响应集) | 较大 | 特别是 Expr、Term、FunctionManager 等类响应集大,因为涉及递归展开和求导。 |
DerivativeFactor:专注于求导运算的表示和求值,内聚高。
RecursiveCallFactor:仅作为递推调用的占位符,具体展开由 FunctionManager 完成,职责单一。
FunctionManager:集中管理函数定义和递推展开,内聚较高,但引入了静态状态(全局缓存、栈),与 OO 原则有冲突。
Validator:单一职责:检查表达式合法性,内聚高。
Monomial:表示单项式,独立且内聚高。
全局管理器:FunctionManager 使用静态方法,导致多处代码(Parser、RecursiveCallFactor、Validator)直接依赖,测试困难,耦合紧。
Parser 与所有因子类:仍然紧耦合,且新增因子需修改 parseFactor。
Poly 与 Monomial:紧密配合,耦合度较高但合理(二者为同一抽象层次)。
Validator 与 Node 树:通过递归遍历,耦合度可接受,但需要知道所有节点类型。
求导功能的实现:derive 方法分布在所有节点中,形成多态行为,降低了耦合。
┌─────────────────────────────────────────────────────────────────────┐
│ Node │
│ ─────────────────────────────────────────────────────────────────── │
│ + toPoly(): Poly │
│ + toCanonicalString(): String │
│ + derive(String var): Node │
└─────────────────────────────────────────────────────────────────────┘
▲
┌──────────────────────────┼──────────────────────────┐
│ │ │
┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐
│ Factor │ │ Expr │ │ Term │
│ (抽象) │ │ │ │ │
└────────┬────────┘ └─────────────────┘ └─────────────────┘
│
┌───────┴───────────────────────────────────────────────────────────────┐
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
Constant Variable ExprFactor ExpFactor FuncFactor Recursive Derivative
(表达式因子) (指数函数) (函数调用) CallFactor Factor
(递推调用) (求导算子)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐
│ Poly │ │ Monomial │ │ FunctionManager │
│ ─────────────── │ │ ─────────────── │ │ ─────────────────────── │
│ coeffs: TreeMap │ │ expX, expY │ │ 静态方法管理函数定义 │
│ (Monomial->Big) │ │ │ │ 和递推展开缓存 │
└─────────────────┘ └─────────────────┘ └─────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Parser │ │Validator │ │MainClass │
└──────────┘ └──────────┘ └──────────┘
关联关系:
- Parser 依赖所有 Node 子类、Sign、Poly
- Expr 包含 Term 和 Sign
- Term 包含 Factor
- Factor 子类可包含其他 Factor 或 Expr
- Poly 使用 Monomial
- FunctionManager 被 RecursiveCallFactor、Parser、Validator 使用
- MainClass 使用 Parser、FunctionManager、Validator
Node:增加 derive(String var) 抽象方法,统一求导接口。
Constant:导数为零。
Variable:根据变量是否匹配求导,结果返回常数或 expr 因子。
ExprFactor:实现链式法则,结果为 n * (内)^(n-1) * (内导数)。
ExpFactor:实现 exp(u)' = exp(u) * u'。
FuncFactor:先展开函数调用,再对展开结果求导。
SelectorFactor:展开后求导。
DerivativeFactor:表示求导算子,在 toPoly/toCanonicalString 中先求导再展开。
RecursiveCallFactor:占位符,实际求导时由 FunctionManager 展开。
FunctionManager:集中管理函数定义(非递推和递推),处理递推展开与缓存。
Monomial:表示 x^a * y^b,用于 Poly 中作为多项式的基。
Validator:检查函数定义合法性(不允许求导、选择器、变量 y 等)。
Parser:扩展解析递推调用、求导算子,支持二元变量。
Poly:改为多元多项式表示,支持 x 和 y。
单元测试:
测试 Monomial 的乘法、比较。
测试 Poly 的多元多项式加法、乘法。
测试每个节点的 derive 方法(常数、变量、表达式因子、指数函数、函数调用、选择器等)。
测试 DerivativeFactor 的 toCanonicalString 和 toPoly。
随机生成测试:随机生成表达式,与已知正确结果对比(使用符号计算库验证)。
这三轮作业做下来,最大的感受就是:写代码容易,但要让代码好改、好加新功能,真的很难。
一开始只处理加减乘除和幂运算,写的挺爽的。但是后面一加新功能,问题就来了。先是要支持 exp函数,接着又加自定义函数、选择器、求导算子、递推函数……每次加新东西,我都得去改 Parser 里的 parseFactor(),那个方法越来越长,从最初十几行膨胀到几十行,全是 if-else 判断。而且 Node 里每加一个抽象方法(比如 derive),所有子类都得跟着实现,有的因子根本不需要求导,也得硬写一个抛异常的方法,感觉有点别扭。
还有一个让我头疼的地方是全局的 FunctionManager。为了管理函数定义和递推展开,我用了静态变量和静态方法,当时觉得挺方便,谁要用直接调用就行。结果后来测试的时候,发现状态会互相干扰,一个用例跑完忘记清理,下一个用例就跑偏了。而且 Parser 里到处是 FunctionManager.get...,耦合得特别紧,想单独测某个功能都费劲。
现在回头想,其实很多地方可以设计得更灵活。比如解析因子的时候,可以弄个“因子注册表”,每种因子自己管自己的解析,这样新增一个因子就不用改 Parser 了。求导、标准化这种操作,也可以抽出来做成独立的访问者,而不是都塞在节点类里。还有就是别再用静态变量存全局状态了,老老实实把依赖通过构造函数传进去,测试起来会轻松很多。
总之,这三轮作业让我明白了一个道理:面向对象不是简单的“继承+多态”,更关键的是怎么让代码在频繁加需求的时候,还能保持干净、好改。有些设计一开始看着简单,但到了后面就成了坑。学会及时重构,多用设计模式,比硬堆代码强太多了。