BUAA OO UNIT1 总结

21231110-司亦菲 2026-04-01 23:48:15

第一次作业

1. 度量分析

1.1 类度量汇总

类名属性个数类名属性个数
Node013
Factor002
Constant128
Variable128
ExprFactor2210
Expr2218
Term2214
Sign003
Parser21110
Poly11295
MainClass017

1.2 方法规模与控制分支数

Node

  • toPoly():抽象方法,不计规模。

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

  • main(String[]):6行,无分支。

2. OO 度量分析

2.1 常用面向对象度量

度量指标说明
DIT(继承深度)最大 2Factor 继承 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 较大但合理。

2.2 内聚性分析

  • Poly:所有方法均围绕 coeff 映射进行多项式运算,内聚度高。

  • Parser:所有方法均用于解析输入字符串,共享 pos 状态,内聚性好。

  • Constant、Variable、ExprFactor:每个类只负责一种因子,职责单一,内聚高。

  • Expr、Term:负责组合因子并转换,内聚良好。

  • Node 抽象层:定义了统一的转换接口,为多态提供基础。

2.3 耦合性分析

  • Parser 与具体 Factor 子类耦合:在 parseFactor() 中直接 new 出 Constant、Variable、ExprFactor,耦合较紧。若增加新因子类型,需修改 Parser。

  • Poly 与 BigInteger、TreeMap:使用标准库,耦合度低。

  • Expr、Term、Factor:通过 toPoly() 返回 Poly,实现了解耦,但 Poly 作为统一表示,所有节点都依赖它,属于依赖倒置。

  • MainClass 只依赖 Parser 和 Poly,耦合度较低。

3. 类图

┌─────────────────┐
│     Node        │
│ ─────────────── │
│ + toPoly(): Poly│
└────────┬────────┘
         │
    ┌────┴────┬─────────────────┐
    ▼         ▼                 ▼
┌─────────┐ ┌─────────┐   ┌─────────────┐
│ Factor  │ │  Expr   │   │    Term     │
│ (抽象)  │ │         │   │             │
└────┬────┘ └─────────┘   └─────────────┘
     │
 ┌───┴────────────────┐
 ▼         ▼          ▼
┌────────┐ ┌────────┐ ┌────────────┐
│Constant│ │Variable│ │ ExprFactor │
└────────┘ └────────┘ └────────────┘

┌────────────┐
│   Poly     │
│ (值对象)   │
└────────────┘

┌──────────┐
│  Parser  │  —— 解析并创建 ExprTermFactor 对象
└──────────┘

┌──────────┐
│ MainClass│
└──────────┘

关联关系:
- Parser 使用 ExprTermFactor 及其子类
- Expr 包含 Term 列表和 Sign 列表
- Term 包含 Factor 列表
- 所有 Node 子类都产生 Poly 对象
- Poly 使用 BigIntegerTreeMap

4. 代码设计

4.1 每个类的设计考虑

  • Node:定义统一转换接口,为表达式树提供多态基础。

  • Factor:抽象因子,为常量、变量、表达式因子提供共同父类,便于 Term 统一管理。

  • Constant / Variable / ExprFactor:分别封装不同类型的因子,每个类职责单一,便于扩展。

  • Expr:表示表达式,由若干带符号的项组成,toPoly() 将各项合并。

  • Term:表示项,由若干因子相乘而成,支持符号。

  • Sign:枚举符号,增强代码可读性。

  • Parser:递归下降解析器,按文法解析表达式、项、因子,职责单一。

  • Poly:多项式表示,支持加法、乘法、幂运算、输出等,作为最终计算结果。

  • MainClass:入口类,负责读入、解析、输出。

5. 测试策略

利用随机表达式生成器,对比手工计算结果与程序输出,验证正确性。

第二次作业

0. 迭代概述

迭代点说明
新增指数函数因子 ExpFactor支持 exp(...) 函数,可带指数
新增自定义函数因子 FuncFactor支持函数定义 f(x)=... 和调用 f(...)
新增选择器因子 SelectorFactor支持条件选择 [ (a==b) ? c : d ]
扩展 Parser增加对 exp、f、[ 的解析
统一输出接口Node 增加 toCanonicalString(),各节点实现标准化输出
引入 Expr 与 Term 的标准化输出支持合并同类项(含 exp 合并)
增加 FunctionDef 类存储函数定义表达式
MainClass 支持读取函数定义第一行输入为函数定义个数(n),若 n==1 则读取函数定义

1. 度量分析

1.1 类度量汇总

类名属性个数类名属性个数
Node024
Factor023
Constant1522
Variable1520
ExprFactor2527
ExpFactor2650
FuncFactor21090
SelectorFactor48110
Expr2680
Term28120
Sign003
Parser214170
Poly11295
FunctionDef128
MainClass1335

1.2 方法规模与控制分支数(重点类)

Node

  • toPoly():抽象。

  • toCanonicalString():抽象

Factor

  • 新增 toExpr():抽象

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

  • toCanonicalString():约70行,包含 try-catch,内部有手动合并同类项逻辑,分支数≈6

Term

  • toCanonicalString():约70行,包含 exp 因子合并逻辑,分支数≈5

  • getMergedNonConstFactors():约40行,分支数≈4

Parser

  • 新增 parseExpFactor()、parseFuncFactor()、parseSelectorFactor()

  • parseFactor() 分支增加,总分支数≈6

2. OO 度量分析

2.1 常用面向对象度量

度量指标说明
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 等类响应集较大。

2.2 内聚性分析

  • ExpFactor:专注于 exp 函数的表示和标准化输出,内聚良好。

  • FuncFactor:专注于函数调用替换,所有方法围绕替换展开,内聚高。

  • SelectorFactor:专注于条件选择,内聚较高,但 normalize 方法涉及多种因子标准化,稍显复杂。

  • Expr / Term:toCanonicalString() 包含了合并同类项、合并 exp 因子的逻辑,职责过重,内聚性有所下降。

2.3 耦合性分析

  • Parser 与所有因子类:parseFactor() 中直接 new 各因子类,耦合紧,违反开闭原则。

  • FuncFactor 与 MainClass:通过 MainClass.getFunctionDef() 获取定义,形成全局依赖,耦合较紧。

  • SelectorFactor 与 Poly:尝试用 Poly.equals() 比较,但 ExpFactor 等无法转为 Poly,因此增加 normalize 回退,耦合复杂。

  • Expr / Term 与 Poly:toCanonicalString() 中尝试使用 toPoly(),若失败则手动合并,两种路径耦合。

3. 类图

┌─────────────────┐
│     Node        │
│ ─────────────── │
│ + toPoly(): Poly│
│ + toCanonicalString(): String
└────────┬────────┘
         │
    ┌────┴────┬──────────────────────┬───────────────────┐
    ▼         ▼                      ▼                   ▼
┌─────────┐ ┌─────────┐        ┌─────────────┐   ┌─────────────┐
│ Factor  │ │  Expr   │        │    Term     │   │   Poly      │
│ (抽象)  │ │         │        │             │   │ (值对象)    │
└────┬────┘ └─────────┘        └─────────────┘   └─────────────┘
     │
 ┌───┴────────────────────────────────────────────────────────┐
 ▼         ▼          ▼           ▼           ▼              ▼
┌────────┐ ┌────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐
│Constant│ │Variable│ │ ExprFactor │ │ ExpFactor│ │FuncFactor│ │SelectorFact│
└────────┘ └────────┘ └────────────┘ └──────────┘ └──────────┘ └────────────┘

┌──────────────┐
│ FunctionDef  │
└──────────────┘

┌──────────┐
│  Parser  │
└──────────┘

┌──────────┐
│ MainClass│
└──────────┘

关联关系:
- Parser 依赖所有 Node 子类、SignPoly
- Expr 包含 TermSign
- Term 包含 Factor
- FuncFactor 依赖 ExprTermFactor 子类
- SelectorFactor 依赖 Factor 子类、Poly
- MainClass 持有 FunctionDef 并传递给 Parser

4. 代码设计

  • 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:增加读取函数定义的逻辑。

5. 测试策略

单元测试:对新增的 ExpFactor、FuncFactor、SelectorFactor 单独测试标准化输出。

随机生成测试:随机生成合法表达式,对比手工推导结果。

第三次作业

0. 迭代概述

迭代点说明
新增求导功能引入 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 合并)

1. 度量分析

1.1 类度量汇总

类名属性个数类名属性个数
Node035
Factor024
Constant1728
Variable2948
ExprFactor2850
ExpFactor2870
FuncFactor411100
RecursiveCallFactor2630
SelectorFactor48110
DerivativeFactor2655
Expr2795
Term210140
Sign003
Parser218210
Poly112110
FunctionManager512100
Monomial2835
Validator0660
MainClass0345

1.2 方法规模与控制分支数(重点新增类)

DerivativeFactor

  • derive() 方法:约 45 行,根据 op 类型分支,内部分为 dx、dy、grad 三个分支,每个分支约 10 行,总分支数≈3。

RecursiveCallFactor

  • 各方法均委托给 FunctionManager.expandRecursiveCall,规模小。

FunctionManager

  • parseRecursiveDefinition:约 25 行,分支(是否为模板 f{n} 或数字索引)。

  • expandRecursiveCall:约 45 行,含多个条件分支(索引是否为 -1/-2、是否缓存等),分支数≈4。

  • expandAllRecursiveCalls 及辅助方法:递归展开,分支较多。

Monomial

  • 简单方法,无分支。

Validator

  • 递归检查表达式树,根据因子类型分支,分支数≈6。

Poly 修改

  • Poly 内部改为 TreeMap<Monomial, BigInteger>,toString 等方法相应调整,逻辑复杂度与之前相当。

Parser 修改

  • 新增 parseDerivativeFactor 方法(约 15 行,分支按算子选择)。

  • parseFuncFactor 扩展支持 f{...} 形式的递推调用,增加了对索引的解析(约 20 行,分支)。

  • parseFactor 中增加对 dx、dy、grad 的识别,分支数增加。

Node 及各个子类

  • 所有节点增加 derive(String var) 方法实现,各方法规模如下:

    • Constant: 1 行
    • Variable: 约 15 行(分支)
    • ExpFactor: 约 15 行(分支)
    • ExprFactor: 约 20 行(分支)
    • FuncFactor: 约 5 行(委托)
    • RecursiveCallFactor: 约 5 行(委托)
    • SelectorFactor: 约 10 行(委托)
    • Expr: 约 20 行(循环合并)
    • Term: 约 35 行(乘积法则,循环加分支)
    • DerivativeFactor: 不实现 derive(抛异常)

2. OO 度量分析

2.1 常用面向对象度量

度量指标说明
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 等类响应集大,因为涉及递归展开和求导。

2.2 内聚性分析

  • DerivativeFactor:专注于求导运算的表示和求值,内聚高。

  • RecursiveCallFactor:仅作为递推调用的占位符,具体展开由 FunctionManager 完成,职责单一。

  • FunctionManager:集中管理函数定义和递推展开,内聚较高,但引入了静态状态(全局缓存、栈),与 OO 原则有冲突。

  • Validator:单一职责:检查表达式合法性,内聚高。

  • Monomial:表示单项式,独立且内聚高。

2.3 耦合性分析

  • 全局管理器:FunctionManager 使用静态方法,导致多处代码(Parser、RecursiveCallFactor、Validator)直接依赖,测试困难,耦合紧。

  • Parser 与所有因子类:仍然紧耦合,且新增因子需修改 parseFactor。

  • Poly 与 Monomial:紧密配合,耦合度较高但合理(二者为同一抽象层次)。

  • Validator 与 Node 树:通过递归遍历,耦合度可接受,但需要知道所有节点类型。

  • 求导功能的实现:derive 方法分布在所有节点中,形成多态行为,降低了耦合。

3. 类图

┌─────────────────────────────────────────────────────────────────────┐
│                              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 子类、SignPoly
- Expr 包含 TermSign
- Term 包含 Factor
- Factor 子类可包含其他 FactorExpr
- Poly 使用 Monomial
- FunctionManager 被 RecursiveCallFactorParserValidator 使用
- MainClass 使用 ParserFunctionManagerValidator

4. 代码设计

  • 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。

5. 测试策略

单元测试:

  • 测试 Monomial 的乘法、比较。

  • 测试 Poly 的多元多项式加法、乘法。

  • 测试每个节点的 derive 方法(常数、变量、表达式因子、指数函数、函数调用、选择器等)。

  • 测试 DerivativeFactor 的 toCanonicalString 和 toPoly。

随机生成测试:随机生成表达式,与已知正确结果对比(使用符号计算库验证)。

心得体会

这三轮作业做下来,最大的感受就是:写代码容易,但要让代码好改、好加新功能,真的很难。

一开始只处理加减乘除和幂运算,写的挺爽的。但是后面一加新功能,问题就来了。先是要支持 exp函数,接着又加自定义函数、选择器、求导算子、递推函数……每次加新东西,我都得去改 Parser 里的 parseFactor(),那个方法越来越长,从最初十几行膨胀到几十行,全是 if-else 判断。而且 Node 里每加一个抽象方法(比如 derive),所有子类都得跟着实现,有的因子根本不需要求导,也得硬写一个抛异常的方法,感觉有点别扭。

还有一个让我头疼的地方是全局的 FunctionManager。为了管理函数定义和递推展开,我用了静态变量和静态方法,当时觉得挺方便,谁要用直接调用就行。结果后来测试的时候,发现状态会互相干扰,一个用例跑完忘记清理,下一个用例就跑偏了。而且 Parser 里到处是 FunctionManager.get...,耦合得特别紧,想单独测某个功能都费劲。

现在回头想,其实很多地方可以设计得更灵活。比如解析因子的时候,可以弄个“因子注册表”,每种因子自己管自己的解析,这样新增一个因子就不用改 Parser 了。求导、标准化这种操作,也可以抽出来做成独立的访问者,而不是都塞在节点类里。还有就是别再用静态变量存全局状态了,老老实实把依赖通过构造函数传进去,测试起来会轻松很多。

总之,这三轮作业让我明白了一个道理:面向对象不是简单的“继承+多态”,更关键的是怎么让代码在频繁加需求的时候,还能保持干净、好改。有些设计一开始看着简单,但到了后面就成了坑。学会及时重构,多用设计模式,比硬堆代码强太多了。

...全文
47 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

302

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧