面向对象设计与构造第一单元总结

孙熙雯-24373420 2026-03-29 00:50:06

1. 基于度量分析程序结构

1.1 类规模与方法复杂度统计

类名属性数方法数方法规模(行/分支)总行数平均方法行数平均分支数
DerivativeFactor243‑8 / 0‑12870.5
ExpFactor255‑16 / 0‑35811.61.4
Expr173‑11 / 0‑1527.40.3
ExprFactor254‑14 / 0‑24591.0
Factor030 / 0620
FuncFactor146‑10 / 0‑1358.80.3
Lexer4233‑13 / 0‑42159.31.1
Main359‑27 / 1‑311723.42.2
NumFactor163‑5 / 0345.70
Parser1174‑28 / 0‑631418.52.5
Poly1105‑34 / 1‑315415.41.7
RecurFuncDef683‑6 / 04860
RecurFuncFactor253‑7 / 0316.20
Result2125‑32 / 1‑526522.12.8
ResultFactor143‑6 / 02460
SelectFactor464‑12 / 0‑2498.20.8
Term297‑28 / 0‑413314.82.1
Token223 / 01260
TokenType00010--

分析

  • ParserLexer 的方法数最多,它们是解析的核心,复杂度较高。
  • Result 类的平均方法行数(22.1)和平均分支数(2.8)最高,说明表达式化简和求导逻辑较为复杂。
  • Main 类的 parseRecurFunction 方法长达27行,分支数3,负责递推函数定义的解析,逻辑较绕。
  • Poly 类的 pow 方法采用了快速幂,分支数2,设计合理。
  • 大部分类的方法规模控制在30行以内,符合良好实践。

1.2 经典面向对象度量

  • DIT(继承深度)
    所有类都直接继承自 Object,除 Factor 接口外无继承关系,DIT=1。
    优点:避免继承带来的复杂性和脆弱性;缺点:缺少利用继承复用代码的机会。
  • NOC(子类数量)
    实现 Factor 接口的类有 10 个:DerivativeFactor, ExpFactor, ExprFactor, FuncFactor, NumFactor, RecurFuncFactor, ResultFactor, SelectFactor, VarFactor
    NOC 较大说明接口的多态应用广泛,符合面向接口编程。
  • CBO(耦合对象数)
    Result 类为例,它依赖 Poly, BigInteger, Map, List 等,并与 Result 自身递归引用,CBO 较高。
    整体上,ResultPoly 之间双向依赖,Parser 依赖 Lexer 和各个 Factor 类,耦合较紧。但通过接口 Factor 隔离了部分依赖,降低了与具体实现的耦合。
  • LCOM(内聚性度量)
    Result 为例,它的两个属性 polyPartexpPart 几乎被所有方法共同操作,内聚性高。
    Parser 类包含多个私有辅助方法,它们都服务于解析任务,内聚性较好。
    Lexer 中每个 handleXxx 方法只处理一种 token,职责单一,内聚性高。

1.3 类图设计与自我点评

(由于无法直接绘制图片,以下用文本描述类图结构)

类图结构描述

img

优点

  1. 接口隔离Factor 接口统一了所有因子的行为,使得 TermExpr 可以统一处理。
  2. 递归结构Expr 可以包含 ExprFactorExpFactor 可以包含任意 Factor,使得表达式可以无限嵌套。
  3. 延迟求值Result 作为求值结果,独立于语法树,便于化简和微分。
  4. 扩展性好:新增因子只需实现 Factor 接口,并修改 ParserTermdeepCopyFactor 等辅助方法即可。

缺点

  1. 部分类职责过重Parser 既要解析表达式,又要解析递推定义,还包含一个内部类 RecurPart,职责不单一。
  2. 重复代码Term 中的 deepCopyFactorreplaceFactor 方法存在大量相似的模式匹配,若新增因子需多处修改。
  3. 全局状态Main 中的静态变量 funcDefrecurDef 使得程序难以测试和复用。
  4. 类型转换:多处使用 instanceof 进行类型判断,违背了多态原则,增加了维护成本。

2. 架构设计体验

2.1 迭代中的架构演化

第一次作业:仅支持常数、幂函数和带括号的表达式(括号深度至多3层),因子只有 NumFactorVarFactorExprFactorResult 仅包含多项式,TermExpr 结构简单。解析时采用了递归下降法,通过 ParserLexer 将输入字符串解析为语法树。

第二次作业:增加了指数函数 exp 和自定义函数 f(x),同时引入选择式因子 [(A==B)?C:D],要求程序能判断 A 与 B 的数学恒等性。为此扩展了 Factor 接口,新增 ExpFactorFuncFactorSelectFactor,并在 Result 中增加了 expPart 来表示指数函数的组合,同时实现了 substitute 方法用于函数实参代入。选择式因子通过在 toResult 时计算 AB 的差并判断是否为零来决策。

第三次作业:表达式扩展为双变量 x, y,新增求导算子 dxdygrad,以及自定义递推函数 f{n}(x)。为此新增 DerivativeFactorRecurFuncFactor,并扩展 Result 支持双变量多项式(Poly 中增加 y 指数)。递推函数在 Main 中使用静态缓存和记忆化搜索展开。求导算子通过 Resultdiff 方法实现,支持偏导和梯度展开。

(这个架构麻烦是麻烦,不过好就好在可拓展性还算可以

2.2 新迭代情景的可扩展性

假设课程组要求新增一种函数类型:对数函数 ln。那么只需:

  1. 定义新的因子类 LnFactor implements Factor,实现 toResult(返回 Result 表示 ln(arg)),diff 按公式 (1/arg)*arg' 实现,replaceX 递归替换参数。
  2. Lexer 中增加对 ln 关键字的识别。
  3. Parser 中增加 parseLnFactor 方法。
  4. Result 中增加一种新的表达式类型(例如 Map<Result, Result> logPart),并实现相应的加法、乘法、微分、代入等运算。
    如果不想修改 Result,也可以将 ln 视为一种特殊的因子,在 toResult 中直接返回一个包装了 LnResult 对象,但需要扩展 Result 的表示能力。

当前的架构中,Result 已经支持多项式与指数函数的组合,但对数、三角函数等新函数需要扩展 Result 的内部表示。这是当前架构的一个局限性——Result 的表示形式固定为多项式加指数函数,难以在不修改核心类的情况下添加新函数。一个更灵活的设计是采用访问者模式代数数据类型,将表达式类型定义为树状结构,但那样会牺牲一些性能。

3. 程序bug分析

3.1 公测与互测中发现的bug

经过代码审查,我发现以下问题和潜在问题:

  1. !?BigInteger?!
    其实正常的都改成了biginteger,但是第二次作业卡了一个点,就是exp没加BigInteger,用的是int。。。
  2. 递推定义解析的健壮性
    Main.parseRecurFunction 使用字符串包含判断(contains("f{0}"))来区分递推定义中的初始值行和递推行。如果递推公式本身也包含 f{0}(如 f{n-1}(x) 不会出现,但若参数中意外出现 f{0} 则会误判)。另外 Parser.parseRecurDefLine 假定递推公式严格为 coeff1*f{n-1}(arg1) + coeff2*f{n-2}(arg2) + extra,且顺序固定,若输入顺序颠倒会导致系数赋错。虽然题目输入格式固定,但设计不够鲁棒
  3. 词法分析冗余与风险
    • handleDerivative 中检测 grad 永远不会触发,因为 d 后跟 g 不构成 grad,该分支冗余。
    • handleRecurFunc 添加 RECUR_FUNC token 后仅移动 pos++,未跳过 {,依赖后续 token 顺序,逻辑正确但易出错。
  4. 导数算子替换后未化简
    DerivativeFactor.replaceX 返回新导数算子,若替换后表达式不含变量,最终求导结果仍为0,但保留算子不产生错误,因为 toResult() 时才求导。

3.2 bug方法与非bug方法的复杂度对比

我统计了本程序中可能出错的几个方法(如 Main.expandRecurFuncParser.parseRecurDefLineResult.substitute)的圈复杂度(按控制流语句数近似估算),并与一些稳定方法(如 NumFactor.toResultVarFactor.toResult)进行对比:

方法代码行数圈复杂度(估算)是否出错
Main.expandRecurFunc235潜在
Parser.parseRecurDefLine286潜在
Result.substitute214潜在
Result.simplify153稳定
NumFactor.toResult31稳定

可见,复杂度较高的方法更容易隐藏逻辑错误。降低方法复杂度的方法包括:拆分方法、使用状态模式、将复杂解析交给专门的类。

4. 测试策略与互测经验

4.1 测试策略

我采用了单元测试+随机测试+边界测试相结合的策略:

  • 单元测试:为每个因子类的 toResultdiff 编写独立的测试用例,验证已知结果(如 dx(x^2) 应为 2*x)。
  • 随机测试:使用 Python 生成大量随机表达式,与 SymPy 计算结果对比,覆盖各种嵌套、指数、导数、递推组合。
  • 边界测试:测试指数为0、1,递推索引为0、1、2,条件表达式恒真/假等情况。

有效性:随机测试发现了导数算子替换后未化简导致的冗余项等问题。

4.2 结合代码设计结构的测试

在互测中,我特别注意了被测程序的代码结构。例如,如果发现某同学的 Result 类没有 simplify 方法,那么我会构造类似 0*exp(x) 的表达式,观察输出是否包含冗余项。

5. 优化分析

5.1 所做的优化

  1. 快速幂Result.powPoly.pow 均采用快速幂算法,避免 O(n) 的乘法。
  2. 合并同类项Result.addResult.multiply 中,对指数部分使用 Map 自动合并,避免结果膨胀。
  3. 延迟化简:只在必要时调用 simplify(如求导后、代入后),避免重复化简。
  4. 递推缓存Main.expandRecurFunc 使用 Map 缓存已展开的递推函数结果,避免重复递归。

5.2 正确性与简洁性

这些优化没有破坏正确性,因为它们都是代数恒等变换(快速幂与普通幂等价,合并同类项不改变值)。但缓存机制存在潜在风险(如前所述),需要保证键的唯一性。另外,simplify 方法中的化简规则需要保证不丢失信息,目前实现了零项去除、合并同底数指数等,是安全的。

6. 大模型使用情况

6.1 代码生成使用率

  • 第一次作业:约 20% 的代码由 AI 生成(主要是词法解析和部分语法树结构)。
  • 第二次作业:约 30% 的代码由 AI 生成(导数算子、指数函数求导规则、Result 的 expPart 设计)。
  • 第三次作业:约 60% 的代码由 AI 生成(递推函数解析、SelectFactor、缓存机制)。

    6.2 其他辅助使用

  • bug 定位:使用 AI 帮助分析递推函数缓存键不唯一的问题,AI 给出了使用 arg.toResult().simplify().hashCode() 的建议。
  • 评测机搭建:AI 生成了 Python 脚本,使用 SymPy 进行结果验证,效果良好,但需要人工调整表达式生成策略。
  • 博客写作:AI 帮助组织语言,但核心分析,框架和数据由我提供。

7. 心得体会

本单元三次作业让我深刻体会到了迭代开发面向对象设计的重要性。第一次作业时,我设计了一个简单的表达式树,只考虑了常数和幂函数。第二三次作业引入指数函数和导数后,我不得不重构 Result 类,增加了 expPart 来支持指数运算,同时扩展了求导规则。递推函数和条件表达式让我进一步体会了延迟求值等的威力。
最大的收获是学会了如何设计一个可扩展的表达式系统:通过接口抽象、组合模式、递归结构,使新功能能够以较小的代价加入。同时,我也认识到过度设计(如过早引入复杂结构)和设计不足(如将求导直接写在 Factor 中)都会带来麻烦。

我本人也在尽量在非常有限的时间和学习两个小专业的课程容易疲惫的状态内做到自己思考,不过度偷懒,但是因为有时候自己想的情况会大幅度挤压完成其他任务和最起码的休息时间,还是不可避免着急过量使用AI。的确很遗憾,我确实会因同学讨论的架构听不懂的状况有些无奈又不甘,其实也希望如果精力充沛,一定会进行一个全面而系统的梳理(土下座
总之,希望自己之后能找到在不吃速效救心丸的基础上平衡多方面精力的方法,毕竟自己之前面对超大量代码,大块的最优时间是半夜,而熬大夜想东西一定会有一段时间心脏不舒服……

8. 未来方向

  1. 强化迭代设计:在第一次作业时多多引导学生思考如何设计一个易于扩展的表达式结构。
  2. 加强测试引导:提供测试工具和评测机框架,鼓励学生编写单元测试和随机测试,减少对互测的盲目性。
...全文
354 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
串联谐振双有源桥(SR-DAB)DC-DC变换器闭环仿真研究内容概要:本文围绕串联谐振双有源桥(SR-DAB)DC-DC变换器的闭环仿真展开研究,重点探讨其在电力电子系统中的动态响应特性与控制策略。通过对SR-DAB变换器建立数学模型,并结合Matlab/Simulink进行闭环仿真,分析系统在不同工作条件下的电压、电流波形及功率传输效率,验证控制算法的有效性与稳定性。研究涵盖系统建模、控制器设计(如PI控制)、软开关实现条件、抗干扰能力以及动态调节性能,旨在提升变换器在新能源、储能、数据中心供电等场景中的可靠性和能效水平。; 适合人群:具备电力电子、自动控制理论基础,从事新能源、微电网、电力系统仿真等相关领域的科研人员与工程技术人员。; 使用场景及目标:①用于高校及科研机构开展DC-DC变换器控制策略的教学与实验;②为工业界在高效率隔离型功率变换系统的设计与优化提供仿真依据和技术支撑;③服务于数据中心、电动汽车、可再生能源并网等场景下的电源系统研发。; 阅读建议:建议读者结合Matlab代码实践仿真流程,重点关注系统建模与控制器参数整定部分,通过调整负载、输入电压等条件观察系统响应,深入理解闭环控制对变换器性能的影响机制。

309

社区成员

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

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