U4 OO 博客作业

孙熙雯-24373420 2026-06-19 22:51:21

一、正向建模与两阶类图的作用

Unit4 的三次作业(hw13→hw14→hw15)是课程中唯一采用"先建模,后编码"正向开发流程的单元。核心工具是 StarUML 中绘制的两阶类图——类图和状态图。

1.1 类图 —— 静态蓝图

在每个作业编码前,我先在 StarUML 中绘制完整的类图,确定三个要素:

  • 类与属性:明确系统中的类及其职责。例如 BookCopy 持有 IsbnBookStateArrayList<MovingTrace> 三个核心属性;User 在 hw15 中扩展 credit(信用分,初始100,上限180)、dueDates(到期日映射)、penalizedOverdue(已罚逾期去重集)等信贷字段。
  • 方法与签名:为每个类定义公开方法,作为后续编码的接口契约。
  • 类间关系Library/LibraryManager 作为门面组合 BookshelfBorrowReturnOfficeAppointmentOfficeReadingRoom 等地点组件,各组件间互不感知,符合"高内聚、低耦合"原则。

类图的正向作用:强制架构思考前置——在编码前就必须回答"每个类知道什么、能做什么";依赖方向在类图中一目了然;从 hw13 到 hw15 每次迭代先改图再改码,类图成为变更的可视化记录。

1.2 状态图 —— 动态行为建模

BookCopy 的状态转移是系统最核心的动态行为。hw13 设定了 4 个状态(BOOKSHELF、BORROW_RETURN_OFFICE、APPOINTMENT_OFFICE、USER)约 10 条迁移。hw14/hw15 扩展为 6 个状态(新增 TREASURED_BOOKSHELF、READING_ROOM),迁移数增至 30+。

@Trigger 注解将状态图与代码精确绑定。每个可能触发状态转移的方法都用 @Trigger(from = "...", to = "...") 标注,使 UML 模型可以自动验证代码是否正确实现了设计的状态机。状态图确保了三件事:每个状态下所有 Trigger 都有定义、无死状态(每个状态有入必有出)、无矛盾 Guard。

1.3 双图协同

类图和状态图不是孤立的。类图中的方法(如 borrowBookreturnBook)对应状态图的 Trigger;状态图的 Guard(如 credit > 80)对应类图中属性的值域约束。实际工作流程为:需求→修改类图→更新状态图→交叉验证(方法是否覆盖所有 Trigger?Guard 在代码中是否有对应判断?)→编码实现。这种双图驱动让 hw15 即使新增信用系统、逾期罚款、续借、奖惩等大量功能,核心架构依然稳定,没有"改一处崩全局"。


二、架构设计与 UML-代码追踪关系

2.1 三次迭代

  • hw13 —— 基础图书馆Library 作为门面(Facade 模式),组合 BookshelfBorrowReturnOfficeAppointmentOffice 三个地点组件,以及 HashMap<String, User> 管理用户。BookCopy 在 4 个状态间流转。借阅规则(B类限1本、C类限每种1本)由 BorrowLimit 静态工具类集中管理。整理流程在 Open/Close 时触发:BRO→BS、BS→AO(预约到书)、AO超期→BS。
  • hw14 —— 阅读室与珍本书架Library 升级为 LibraryManager,新增 ReadingRoom 和珍本书架(treasuredShelfBookshelf 的第二个实例)。引入评分系统后,书籍在普通书架和珍本书架之间迁移取决于 isTreasured(isbn) 的代码判断——这是一个关键设计决策:书架选择逻辑放在代码中而非 UML Guard 条件中,因为评分是运行时动态变化的。新增 READRESTORE 操作,以及 GRADE 评分命令。
  • hw15 —— 信用系统User 新增信贷子系统:credit(100起,180上限,0下限)、dueDates(B类15天、C类30天到期)、penalizedOverdue(防重复罚款的去重集合)。信用奖惩机制:按时归还 +10、按时归还阅读书籍 +10、逾期每天 -15、预约未取 -15、未归还阅读书籍 -10。新增 RENEW 续借(仅未逾期时可续,延长7天)和 query_credit 查询命令。Guard 条件映射到信用门槛:credit > 80 可借阅/预约/取书、credit > 40 可读A类书、credit > 0 可读B/C类书。

2.2 UML 与代码的追踪关系

UML 元素代码对应追踪机制
UMLClassJava 类类名一一对应
UMLAttributeJava 字段属性名与类型对应
UMLOperationJava 方法@Trigger 注解绑定
StateBookState 枚举状态名对应枚举常量
Transitioncopy.move() 调用@Trigger(from, to) 注解
Guardif 条件判断Guard 文本→代码条件表达式

@Trigger 注解是最强的追踪纽带。例如 @Trigger(from = "BOOKSHELF", to = "USER") 修饰 borrowBook 方法,从 UML 状态图出发可定位到精确的代码实现方法,从代码出发可反向验证 UML 中是否有对应迁移——形成双向可追溯链

2.3 Guard 条件修复案例

在 hw15 开发中,状态图中 14 条 Guard 条件存在矛盾——进入珍本书架需 avgScore >= 4,离开需 avgScore < 4,两个互斥条件导致某些路径在形式语义上不可达(评测反馈:"从起始状态到 Bookshelf 的某条简单路径无解")。修复方案:将这些 Guard 从 UML 状态图中删除,改为在 moveToShelf() 方法中通过 isTreasured(isbn) 动态判断。最终保留了 4 条有意义的 Guard(credit > 80credit > 40credit > 0),因为它们表达的是稳定的业务权限规则。

启示:UML 模型适合描述静态业务规则约束,不适合表达运行时数据动态性。正向建模不是"画完图就完事"——编码中发现的模型缺陷需反哺到 UML 图中,形成双向修正闭环。


三、大模型辅助正向建模的体验

继续使用 DeepSeek 辅助开发。与前三个单元专注于"怎么写代码"不同,本单元的核心挑战是"怎么设计模型"——这正是大模型相对薄弱的领域。

大模型擅长的

  • UML 格式生成:StarUML 的 .mdj 文件本质是 JSON。想好类的设计后让模型生成对应的 JSON 节点,避免手工编辑的繁琐和格式错误。
  • 设计审查:曾指出 LibraryManager 早期版本同时持有 Bookshelf 引用和 BookCopy 直接列表——这是"穿透式"依赖,应统一通过 Bookshelf 访问 BookCopy
  • 状态覆盖分析:在 6 状态 × 10+ Trigger 的复杂空间中帮助检查死状态和遗漏的 Trigger 组合。
  • 重构方案生成:Guard 条件修复时给出多种方案,其中"将书架选择逻辑移到代码中"最终被采纳。

大模型不擅长的及引导策略

  • 业务规则理解不足:无法自行判断"为什么珍本书架门槛是 ≥4"这类课程需求。对策:将相关需求规格原文嵌入 prompt。
  • 架构权衡判断:Guard 放 UML 还是放代码,模型给不出有依据的建议。对策:在 prompt 中明确约束——"Guard 仅包含静态业务规则"。
  • 多图一致性维护:修改类图时容易忽略状态图的联动。对策:同时给出两图内容,明确要求交叉检查。

有效引导策略总结

  1. 分层提问:类识别 → 关系建模 → 行为建模,逐层推进,避免 prompt 过长导致注意力分散。
  2. 约束边界 + 开放设计:给定接口规范和框架约束,开放内部方法设计空间。
  3. 迭代审查:"生成 → 审查 → 反馈 → 修改"循环,不期望模型一步到位。
  4. 具体案例驱动:用带数据的场景(如"评分 3.5 的书在 TBS 状态,handleOpen 后迁移到哪里?")代替抽象的"检查完整性",能更有效地暴露逻辑漏洞。

四、四个单元架构设计思维的演进

  • Unit1(算法驱动):架构 = 递归下降解析器 + AST 层次(ExprTermFactor)。将数学语法直接翻译为类结构,设计服从算法。局限:前瞻性不足,hw2→hw3 扩展多变量和导数时 Poly 内部表示经历了较大重构。
  • Unit2(并发模型):从静态类层次转向动态线程交互。架构核心是生产者-消费者模式(InputThreadRequestTableDispatcherCar)和锁粒度设计。认识到架构不仅是空间结构(类关系),也是时间结构(锁依赖图、线程等待链)——死锁风险来自后者。
  • Unit3(契约驱动):JML 让我接触"设计即契约"的理念。架构是对形式化接口(NetworkInterface 等)的精确实现,requires/ensures/assignable/signals 构成不可违反的约束边界。设计的好坏首先取决于"是否正确",而非"是否巧妙"。
  • Unit4(模型驱动):设计被严格置于编码之前。起初不确定"万一图里的设计跑不通怎么办",但实际经历表明:如果状态图自身完备且自洽,代码基本不会出现结构性缺陷。问题总是出在图的逻辑本身(如 Guard 矛盾),而这恰恰在设计阶段就能通过模型检查发现——比编码后跑测试高效得多。
Unit1: 算法→类层次      Unit2: 类层次→线程协作
Unit3: 自由设计→契约约束    Unit4: 契约约束→模型驱动

从"代码能跑就行"到"编码前在模型层面推理验证"——这大概就是"设计与构造"的真正含义。


五、四个单元测试思维的演进

  • Unit1(用例驱动黑盒测试):手工枚举表达式类型,运行程序比对输出。局限:测试完备性依赖经验,hw3 引入导数和递归函数后组合爆炸,手工枚举力不从心。
  • Unit2(日志驱动并发测试):并发 bug 是概率性的,同一数据可能 8 次正常 2 次死锁。手段:TimableOutput 还原事件序列定位死锁点、TimableInputStream 时间加速在秒级重现分钟级场景、用大模型构造极端边界数据(全员同层、大量维护并发、F2 碰撞边缘等)。
  • Unit3(规格驱动参数化测试):JUnit4 @Parameterized 让 13 种图结构共用约 20 行测试逻辑。从 JML 规格推导测试:requires→等价类划分、ensures→验证断言、signals→异常优先级测试序、assignable \nothing→状态快照对比。
  • Unit4(模型驱动完备性验证):测试对象扩展至 UML 模型本身——编码前检查状态图完备性(可达性、无死状态、无矛盾 Guard),编码后通过 @Trigger 验证一致性,最后用全生命周期场景(购入→上架→借出→归还→预约→取书→评分→入/出TBS→逾期→续借)验证端到端状态流转。
Unit1: "测我能想到的"      Unit2: "测那些概率性出现的"
Unit3: "测规格所要求的"    Unit4: "测设计本身的正确性"

六、课程收获

技术层面

OOP 核心概念(封装、继承、多态、抽象)在四个单元的多范式实践中内化:递归下降解析→多线程并发→契约式设计→模型驱动开发。每个单元的编程范式差异很大,避免了陷入单一思维定式。Git、JUnit、StarUML、IDE 等工具链已成肌肉记忆。大模型使用形成了明确的"人机分工"认知——模型是执行助手,架构决策仍需人的判断。

思维层面

最大转变:从"如何让代码跑起来"到"编码前如何设计正确、可扩展、可验证的系统"。JML 让我认识到正确性可以通过形式化契约系统追求;UML 让我认识到设计是可以独立于编码进行并提前验证的。hw13→hw15 的顺利迭代证明:好的初始设计让扩展变得自然,好的抽象让变更的涟漪最小。

个人成长

四个单元的 bug 类型完全不同——逻辑错误(U1)、死锁/活锁(U2)、JML 实现偏差(U3)、Guard 条件矛盾(U4)——每种都需要不同的 debug 策略。这培养了"先定位问题类型,再选择分析工具"的元能力。Unit3 击鼓传花游戏更是让我深刻体会到规格精确性在协作中的重要性——一个微小歧义传递 6 次后变得面目全非。

"面向对象设计与构造"——设计在前,构造在后。这不再是课本上一句话,而是四个单元下来亲身验证的道理。

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

308

社区成员

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

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