OO第四单元正向建模与UML总结

林之吟-24373035 2026-06-26 00:53:28

OO第四单元正向建模与UML总结

一、对正向建模与开发的理解

UML 是软件工程领域的标准建模语言,类图是其中最核心的工具。说白了,它是一张写代码之前的设计蓝图,根据我们的想法绘制,然后指导我们撰写代码。

在第四单元之前,我写 OO 作业都是把指导书看完,直接写代码,把构思的任务交给 AI。这就导致我之前写代码的时候,总有一种云里雾里、当局者迷的感觉——我不了解整个框架,不知道自己代码完成到什么程度,直到代码能跑通,我才知道"哦,我写完了"。这个单元很好地帮助我在动手写代码之前先进行构思,而且我发现这样做反而更加迅速高效。

正向建模的核心价值在于:先想清楚再动手。类图逼着你在编码前回答三个问题:系统里有哪些核心对象?每个对象承担什么职责?对象之间如何协作?这三个问题想清楚了,代码实现就变成了按图施工。

1.1 一阶类图:设计蓝图

一阶类图是在编码前的架构设计阶段完成的。它的核心作用是清晰表达系统的核心职责划分整体结构,而不是对代码中所有属性与方法进行事无巨细的抄录。

我画一阶类图时遵循的原则是:

  • 命名具象化,贴合业务:用 BorrowAndReturnOffice 做类名,而不是 Office;用 returnBook 做方法名,而不是 handle。OO加油站公众号提到的"关键词覆盖率"指标,本质上就是检测类和方法命名是否过于空泛。
  • 关注核心而非细节:不需要罗列 getter/setter 这样的辅助方法,只写核心的公开方法、关键属性和类之间的关系。

1.2 二阶类图:施工图纸

二阶类图是在代码完成过程中对一阶类图进行修正后的实现版本。它必须精确匹配代码:类名、属性名、类型、可见性、方法签名、返回参数、关联关系,一个都不能错。

从一阶到二阶的演进过程中,我的一阶图里 Library 只有几个核心方法,到了二阶图,我增加了 findOrAddUser 这个私有方法——因为编码时发现需要频繁根据学号查找或创建用户,提取出来可以复用。另外,我在 Library 中直接建了一个 copyMap 属性,把所有的 BookCopy 按 ID 存起来,这样查询轨迹时可以直接 O(1) 定位,不需要在每个地点类里逐个寻找。这些调整都体现了设计随编码而演进的自然过程。

1.3 两阶类图在正向建模中的作用

两阶类图的设计体现了正向建模的核心思想:设计是迭代的。一阶类图允许抽象,方法参数可以简略,不画私有成员;二阶类图则必须精确,与代码严格一致。这种从抽象到具体的演进,让我深刻理解了"设计先行"的价值——不是追求一次完美,而是在设计→编码→修正的循环中不断完善。

二、架构设计与UML模型追踪

2.1 整体架构设计

图书馆系统的核心是管理图书副本的生命周期。我采用的架构是中心管理 + 地点存储模式:

  • LibraryManager:总控类,持有所有地点和学生数据,处理所有命令
  • BookCopy:图书副本实体,记录状态、轨迹、预约/借阅信息
  • 地点类:BookShelf(书架)、BorrowAndReturnOffice(借还处)、AppointmentOffice(预约处)、ReadingRoom(阅览室)
  • Student:用户管理,记录借阅、预约、阅读状态和信用分

2.2 数据结构选择的演进

三个地点的数据结构选择体现了"由业务操作决定"的原则:

地点核心操作数据结构选型理由
书架按ISBN快速取任意一本可用副本Map<ISBN, Set<BookCopy>>借书需要O(1)定位
借还处整理时批量取出全部List<BookCopy>只需要全部取出,够用就好
预约处按学生取书 + 按过期日期批量清理Map<学生ID, BookCopy> + TreeSet<BookCopy>双索引,两种查询都高效

预约处的双索引设计是我比较满意的地方。如果只用 HashMap,每次清理都要遍历所有预约书;如果只用 PriorityQueue,只能取堆顶,无法实现取书时的随机删除。所以用了双索引——TreeSet 按过期日期排序,清理时遇到第一个没过期的就可以 breakHashMap 按学生 ID 取书 O(1)。

这里有一个坑:TreeSet 默认按 compareTo 判断相等,如果只按过期日期比较,同一天过期的多本书会被认为是同一个元素。解决方法是用复合比较器:先按过期日期排,再按 bookId 的字符串排。

2.3 代码与UML模型的追踪关系

在开发过程中,我保持了代码和UML模型之间的追踪关系。具体做法是:

  • 类与类图一一对应:每新增一个类,先在类图中补充,再写代码
  • 关键方法同步:核心方法(如 borrowBookreturnBookarrangeBook)在类图和代码中保持一致
  • 状态图与状态转移BookCopy 的状态转移(书架→用户→借还处→书架)在状态图中明确画出,代码中用 @Trigger 注解标注

实际操作中,我的顺序是:先画一阶类图(设计阶段)→ 写代码 → 根据代码修正二阶类图。这样既保证了设计的指导性,又允许编码过程中的灵活调整。

2.4 整理策略的设计与迭代

整理流程是图书馆系统中最复杂的部分。我的整理顺序经历了从复杂到简单的演进:

初版设计:在整理时直接从借还处取书送到预约处,减少移动次数。但实现起来比较复杂,需要处理多个来源的书籍。

最终简化:观察规则后发现"终点为预约处的移动不计入次数限制",于是干脆先把借还处和过期书籍全部清空放回书架,然后统一从书架取书满足预约队列。这样做虽然某些书可能被移动了两次(先从借还处到书架,再从书架到预约处),但因为第二次的终点是预约处,不计入次数限制,所以完全合法,而且实现起来简单得多。

开馆前整理需要额外处理精品书架的归类,我采用了只处理今日被评分ISBN的优化策略——用 ratedIsbnToday 记录今天被评分的ISBN,整理时只检查这些ISBN对应的书籍是否需要移动。这样避免了每次整理都遍历所有书,效率更高。

三、大模型辅助正向建模的实践

3.1 正向建模中的使用场景

我在本单元中使用大模型辅助了以下场景:

  • 生成初始类图框架:用自然语言描述需求,让大模型给出类图建议,再根据实际情况调整
  • 补充关键词覆盖:根据指导书的关键词列表,检查类图是否覆盖,补充缺失的关键词
  • 数据结构选型建议:当不确定用 HashMapTreeSet 还是 List 时,向大模型描述业务场景,获取选型建议

3.2 引导大模型完成架构设计的经验

在实践中,我发现引导大模型的关键在于提供足够的上下文

  • 不仅要告诉它"我要实现图书馆系统",还要告诉它"图书有A/B/C三类,有不同的借阅限制"
  • 让大模型先理解业务场景,再给出设计方案,而不是直接问"用什么数据结构"

但是,大模型在生成类图时经常会忽略一些细节。例如,我曾让大模型生成 Student 类的设计,它给出了 credit 属性,但忽略了信用分变化时机的具体规则(逾期还书扣15、预约不取扣15、按时还书加10),这些需要在后续对话中补充。

3.3 大模型的局限

大模型在正向建模中的主要局限是:

  • 容易忽略边界条件:比如信用分降到0后不能再预约,但大模型生成的代码可能只在借书时检查信用分,而忘记在预约时也检查
  • 过度设计或设计不足:有时大模型会建议在 BookCopy 中同时保存多个日期(借阅日期、续订日期、应还日期等),但实际上只需一个 borrowedExpireDate 就够了
  • 无法理解具体的评测规则:大模型不知道指导书中的状态图评测规则(如Guard条件互斥、路径有解等),所以生成的状态图可能需要大量手工修正

总的来说,大模型适合作为设计助手,给出初步方向和建议,但最终的架构决策和细节调整还需要自己来完成。在第四单元,我实际设计出的 TreeSet 复合比较器和整理顺序的优化,都是在大模型建议基础上结合对业务场景的深入理解得出的。

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

4.1 第一单元:从"能跑就行"到"设计优先"

第一单元我基本是看到需求就直接写代码,没有架构设计的概念。表达式展开的需求虽然不算复杂,但因为没有事先规划,经常出现改一个地方要修改多个类的情况,代码耦合度极高,扩展起来非常痛苦。

4.2 第二单元:开始考虑"模块划分"

第二单元的电梯调度让我第一次意识到模块划分的重要性。我划分了 Elevator(电梯)、Scheduler(调度器)、Request(请求)等模块,但模块间的职责划分仍不够清晰,比如调度器既负责分配电梯又负责控制电梯运行,导致代码臃肿。

4.3 第三单元:规格驱动开发

第三单元引入了 JML,我学习了规格驱动开发的方法论。这个单元让我养成了"先读规格再写实现"的习惯,也让我理解了接口与实现分离的设计思想。但JML主要针对方法级别的规格,缺少对全局架构设计的指导。

4.4 第四单元:正向建模与设计迭代

第四单元让我真正建立了"设计先行"的工作流。从一阶类图到二阶类图的演进过程,让我理解了设计不是一次成型,而是通过迭代不断完善的。具体来说:

  • 一阶类图阶段:我提取了核心名词(图书馆、书架、借还处、预约处、图书、用户),确立了核心类,明确了每个类的核心职责
  • 编码阶段:在实现过程中发现了设计的不合理之处(比如一阶中 arrange 没有参数,但编码时需要区分开馆前/闭馆后整理)
  • 二阶类图阶段:根据编码中的发现修正类图,补充了 findOrAddUser 等辅助方法,细化了参数类型

这个迭代过程让我理解了两阶类图差异的意义:一阶是蓝图,二阶是施工图,它们可以不同,但要诚实记录变化原因(重构说明)。

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

5.1 第一单元:手动测试为主

第一单元我主要依靠手动测试,在本地跑几个样例,看看输出对不对。这种方式效率极低,而且覆盖不到边界情况。

5.2 第二单元:随机测试的尝试

第二单元我开始尝试随机测试。电梯调度的正确性涉及时间顺序,我编写了随机请求生成器,对比不同调度策略的输出,但由于多线程的复杂性,测试结果难以判断是否"正确"。

5.3 第三单元:规格驱动的JUnit测试

第三单元的JML规格让测试变得有据可依。我写了大量的JUnit测试用例,覆盖正常路径和异常路径。在测试 pure 方法时遇到了浅拷贝问题,采用了双 Network 方案解决。我还在测试中使用了随机测试,固定随机种子来保证可复现性。

5.4 第四单元:交互式评测的理解

第四单元的评测方式是交互式的——只给出借阅/预约/阅读/查询/评分请求,而还书/取书/归还/续订请求由评测机根据输出动态生成。这要求我的程序保持状态一致性,任何错误状态都会在后续请求中被放大。

我测试的重点放在了:

  • 状态转移的正确性:每本书的轨迹是否与实际操作一致
  • 信用分计算的准确性:每个事件触发的加减分是否正确
  • 整理流程的顺序:开馆前/闭馆后的整理是否正确清空了应清空的地点

六、课程收获

通过四个单元的学习,我最大的收获是理解了"设计先行"的价值。从第一单元的"写完再说"到第四单元的"先画图再编码",我的工作方式发生了根本性的变化。虽然画类图看起来是额外的工作量,但实际上它让编码阶段更加高效——因为核心决策已经在设计阶段做完了。

这个学期让我明白,设计不是一次完成的,而是在编码过程中不断演进的。一阶类图和二阶类图的差异不是错误,而是迭代的自然结果。重要的是要诚实记录变化原因,让设计文档反映真实的代码状态。

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

309

社区成员

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

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