OO 第四单元博客

关皓元-24371295 2026-06-22 14:40:26

OO 第四单元博客

回看 2026 年 OO 课程,四个单元像是四种不同尺度的建模练习。第一单元从表达式文法出发,把输入文本变成 AST,再变成可计算、可化简的代数对象;第二单元进入多线程电梯,重点变成线程协作、调度策略和运行时状态;第三单元围绕 JML 规格实现社交网络,训练的是契约、异常、查询复杂度和单元测试;第四单元则把 UML 模型直接放到开发流程之前,要求先建模,再实现,再用二阶类图、状态图和顺序图校准设计。

第四单元的三次作业尤其适合总结正向建模。它不是简单地“先画图再写代码”,而是让模型成为需求理解、职责拆分、增量开发和最终追踪的中间语言。我的最终代码中,LibraryLibraryManagerRequestHandlerArrangeServiceBookshelfAppointmentOfficeBorrowAndReturnOfficeReadingRoomGradeOfficeCreditPolicyLoanPolicy 等类都能在 UML 中找到对应位置;同时,代码中一些为了输出和评测服务的实现细节,也反过来促使二阶类图修正。

第四单元的正向建模与开发

第 13 次作业建立了图书馆系统的核心领域模型:图书馆、用户、图书、图书副本、书架、借还处、预约处、移动轨迹和借阅限制。此时最关键的不是算法,而是把“图书副本的所有权/位置变化”建成系统主线。借书是书架到用户,还书是用户到借还处,预约和取书引入预约处,查询依赖移动轨迹。于是我在一阶类图中把 BookCopy 作为可移动实体,把 MovingTraceMoveRecord 作为行为日志,把 BorrowLimit 独立为规则判断类,避免把规则散落在输入处理里。

第 14 次作业加入精品书架、阅读、归还和评分。这个增量的本质是:图书副本的状态空间扩张了,规则也从“能不能借/预约”扩展到“能不能读、评分是否导致精品状态变化、整理时应该回到普通书架还是精品书架”。因此模型中增加了 TreasuredBookshelfReadingRoomGradeOfficeGradeRecord。状态图开始变得重要,因为 BookCopy 不再只在书架、借还处、预约处和用户之间移动,还会进入阅览室,并在整理流程中重新归位。

第 15 次作业加入信用分、借阅期限和续借。这里如果继续把规则写进 Library,代码会迅速变成条件分支堆叠。因此我把信用分变化抽成 CreditPolicy,把到期日、逾期判断和续借抽成 LoanPolicy,把每一次借阅事实抽成 LoanRecord。这样 Library 仍然负责业务编排,但具体规则可以由策略类承接。最终的重构说明也记录了这一点:二阶类图根据最终代码补入了 CreditPolicyLoanPolicyLoanRecord,并同步了还书、取书、预约失效、阅读未归还等流程中的信用分结算。

两阶类图的作用

我对两阶类图的理解是:一阶类图是正向建模阶段的设计假设,二阶类图是实现完成后的设计校准。二者不是重复劳动,而是同一套设计在两个时刻的截面。

一阶类图的价值在于提前建立词汇表。第四单元需求很长,且有大量自然语言规则。如果直接写代码,很容易把“书”“某本书”“某类书”“用户持有”“预约有效”“整理移动”等概念混在一起。一阶类图迫使我先区分 BookBookCopy,区分 Isbn 和完整副本号,区分 OrderRecord 和预约处实际持有的副本。这些名字一旦稳定,后续读指导书、和大模型讨论、写代码、定位 bug 都更顺畅。

一阶类图还承担了增量开发的边界预测。hw13 中抽出的 BorrowLimitArrangeServiceMovingTrace 在 hw14 和 hw15 中都继续存在,说明第一版模型抓住了稳定轴线。即使后来增加精品书架、阅览室、信用分和借阅期限,系统仍围绕“请求处理、领域规则、场所容器、图书副本移动、移动记录”展开,没有推倒重来。

二阶类图的价值在于把实现中的真实复杂度带回模型。实现时我发现,有些细节在一阶类图中低估了:比如借阅期限不是 BookCategory 上一个简单常量,而是要与 LoanRecord、还书日期、开闭馆结算和续借协作;信用分也不是用户上的普通属性,而是一组跨流程触发的奖惩规则。这些内容最终进入了 CreditPolicyLoanPolicyLoanRecord。二阶类图将这些新增职责显式化,使 UML 不只是开发前的草图,而成为开发后的可追踪文档。

二阶类图还帮助控制“模型和代码脱节”。指导书中有类图与程序一致性检查、关系检查、语义完整性检查和两次类图相似度检查,这使得二阶类图不能随便重画。它必须解释为什么重构,同时保留一阶类图的核心结构。对我来说,这等价于一次设计复盘:哪些类是稳定抽象,哪些类是实现后才暴露出来的策略对象,哪些字段只是为了输出或评测而存在。

第四单元架构设计与追踪关系

最终架构可以分成五层。

第一层是输入与输出适配。InputCommand 解析一行输入,识别开馆、闭馆、借书、还书、预约、取书、查询、阅读、归还、评分、续借和信用分查询。RequestHandler 把命令转换为 Library 的领域方法调用,并负责输出格式,例如借阅成功后输出具体副本号,还书成功后输出是否逾期。

第二层是系统编排。LibraryManager 初始化图书和副本,持有各个办公室、书架和 Library,并在收到 OPENCLOSE 时调用 ArrangeService。它更像应用层入口,不直接判断复杂业务规则。

第三层是领域核心。Library 是最集中的业务编排类,负责借书、还书、预约、取书、阅读、归还、评分、续借、信用分查询、整理移动和轨迹记录。它不直接保存所有规则常量,而是组合 BorrowLimitCreditPolicyLoanPolicy 等规则对象。

第四层是领域实体与场所容器。User 保存持有副本、借阅记录、有效预约和信用分;Book 保存 ISBN、总副本数、评分统计和精品标记;BookCopy 保存具体副本、当前位置、持有者、阅读者和预约对象。BookshelfTreasuredBookshelfBorrowAndReturnOfficeAppointmentOfficeReadingRoom 分别代表副本在不同场所中的集合。

第五层是审计和辅助模型。MovingTraceMoveRecord 记录图书副本的历史移动;OrderRecord 记录预约生命周期;GradeRecord 记录评分;LoanRecord 记录借阅生命周期。状态图中的六个主要状态:普通书架、精品书架、借还处、预约处、阅览室、用户,基本对应 LocationType 的取值,也对应 BookCopy.moveTo 上的 @Trigger 注解。

代码和 UML 的追踪关系大致如下。

UML 元素代码落点追踪说明
LibraryManagerLibraryManager.java初始化对象图,处理开闭馆,转交普通请求
RequestHandlerRequestHandler.java请求到领域方法的适配层,集中输出格式
LibraryLibrary.java领域编排核心,连接场所、规则、记录和用户
ArrangeServiceArrangeService.java开馆前/闭馆后整理,触发预约过期、预留、归架、信用结算
BookBookCopyIsbn同名 Java 文件区分 ISBN、图书概念和具体副本,支撑副本级移动追踪
BookshelfTreasuredBookshelf同名 Java 文件普通书架与精品书架分离,按 ISBN 管理可用副本
BorrowAndReturnOfficeAppointmentOfficeReadingRoom同名 Java 文件对应借还处、预约处、阅览室三个业务场所
BorrowLimitCreditPolicyLoanPolicy同名 Java 文件将借阅限制、信用规则、借阅期限从主流程中抽出
OrderRecordLoanRecordGradeRecordMoveRecord同名 Java 文件记录预约、借阅、评分、移动这些可审计事实
状态图状态和迁移LocationTypeBookCopy.moveTo用状态枚举和触发注解承接状态图
顺序图消息Library.orderNewBookUser.getOrderedBook用锚点方法满足预约和取书场景的顺序图追踪

最终代码和 UML 并非机械一致。最明显的差异有三类。第一类是实现性字段,例如 lastResultBookCopyCodelastReturnOverdue,它们服务于输出格式,并不是核心领域概念。第二类是建模阶段的职责迁移,例如一阶或预设计中曾倾向让 BorrowLimit 管更多规则,最终实现中阅读权限进入 CreditPolicy,期限规则进入 LoanPolicy。第三类是评测和图检查所需的锚点,例如 orderNewBookgetOrderedBook 本身业务逻辑很轻,但在顺序图追踪中有明确意义。

这说明 UML 到代码的追踪不是“每个方法逐字一致”这么狭窄,而是要保持职责、状态、关键协作和领域对象的一致。只要能解释每次偏移背后的实现原因,二阶类图就不是事后补作业,而是设计闭环的一部分。

使用大模型辅助正向建模的体会

大模型在复杂场景中很适合做“建模陪练”,但前提是不能只给一句“帮我设计架构”。第四单元的需求有日期、状态、场所、信用分、交互式评测和 UML 检查,如果提示不够结构化,大模型往往会给出看似完整、实际无法落地的泛化设计。

比较有效的引导方式是先让大模型复述领域词汇。比如要求它区分 BookBookCopyIsbnOrderRecordLoanRecord,并列出每个概念的生命周期。这个步骤可以快速暴露混淆:如果模型把“某本书是否在架”和“某 ISBN 是否存在”混为一谈,就说明后面的设计不能直接信。

第二步是把需求改写成状态表和事件表。对第四单元而言,状态表围绕 BookCopy 的位置展开,事件表围绕 borrowed、returned、ordered、picked、read、restored、graded、renewed、OPEN、CLOSE 展开。要求大模型为每个事件给出前置条件、成功效果、失败效果和需要记录的轨迹,比要求它直接生成类图更可靠。

第三步是让大模型做职责分配,而不是一次性产出全部代码。可以明确限制:“输入解析、输出格式、领域规则、场所容器、审计记录分别放在哪里?”这样模型更容易得到 RequestHandlerLibraryArrangeServicePolicyRecord 这样的分层。复杂规则也要要求它指出“哪些规则会在未来作业中变化”,从而决定是否抽策略类。

第四步是让大模型反向审查模型。比如把一阶类图或类列表喂给它,要求检查是否存在上帝类、状态遗漏、重复职责、难以测试的私有逻辑、无法追踪的跨对象副作用。这个过程比让它“重写一个更好架构”更安全,因为它是在已有设计上找洞。

第五步是建立模型到代码的追踪矩阵。每新增一个类,都让大模型回答三个问题:它对应哪条需求?它由谁创建?它的状态变化由谁负责?每新增一个流程,都让它回答:涉及哪些对象?成功路径和失败路径各改变什么?需要写入哪些记录?这种问题会迫使大模型从“生成名词”转向“维护不变量”。

最后,大模型给出的方案必须经过本地验证。对于第四单元,我最需要警惕的是日期边界、预约失效、逾期结算、阅读未归还和同一本副本一天内移动次数。大模型可以帮助罗列这些场景,但不能替代状态图检查、样例推演和交互式测试。它更像是一个随叫随到的架构审稿人,不是最终裁判。

架构设计思维的演进

第一单元时,我的主要思路还是“把表达式算对”。最初关注的是递归下降解析、括号、符号、幂次、函数展开和化简输出。后来逐渐意识到,表达式系统的核心不是 Parser,而是 AST 和代数对象之间的边界。ExprNode.toPoly() 把语法树转换为规范化多项式,PolyMono 负责代数运算、哈希、缓存和输出,这比把所有逻辑堆在 Parser 中更稳定。

第二单元把我从静态数据结构推进到运行时架构。电梯作业中,正确性不仅取决于某个函数返回值,还取决于线程是否能结束、请求是否会丢失、电梯状态是否可被调度器安全观察、双轿厢或换乘逻辑是否会产生等待死锁。因此我开始把架构理解为“对象之间的协作协议”:输入线程、调度器、请求队列、电梯线程、轿厢状态、井道控制器都需要清晰边界。

第三单元的 JML 训练让我把“契约”放在实现之前。社交网络作业里,很多方法的语义已经由规格定义,架构的难点变成如何在满足规格的同时保证性能。于是 VideoIndexInfluenceIndexNetworkQueries 这样的辅助类出现了:主类负责维护容器和异常协议,查询类和索引类负责复杂查询与增量维护。这让我第一次很明确地感受到,好的架构不只是分文件,还要分离“状态维护”和“查询优化”。

第四单元则把这种思维推到模型层。UML 不是代码的附属品,而是需求到实现的中介。我开始先问:领域里有哪些稳定实体?哪些是策略?哪些是生命周期记录?哪些是场所?哪些状态必须画出来?这使我的架构思维从“写完后整理代码”转向“写之前先建立对象世界,写之后再校准对象世界”。

测试思维的演进

第一单元的测试重点是输入空间覆盖和等价性判断。我更多依赖随机生成表达式、边界表达式和参考输出,对 Parser、符号处理、函数展开、条件表达式、求导与化简做差分测试。那时我对测试的理解主要是:造出足够多、足够刁钻的输入。

第二单元的测试让我意识到输出不一定唯一。电梯调度的合法输出有很多种,不能简单逐行比对。因此测试要转向语义检查:乘客是否最终到达,开关门是否合法,载重是否超限,时间是否合理,线程是否退出,是否出现无效移动或死锁。这里的测试思维从“对答案”变成“查不变量和活性”。

第三单元引入 JUnit 后,我开始关注单元测试的可定位性。比如 recommendNthUp 的测试覆盖排名、排除自己和已关注用户、并列时 ID 优先、异常路径以及查询是否不修改对象状态。JML 规格天然适合把前置条件、后置条件和异常条件拆成测试用例,测试也不再只是黑盒输入输出,而是能检查对象状态是否保持一致。

第四单元的测试更接近模型驱动。图书馆系统的难点在于跨天、跨状态、跨对象协作,尤其是交互式评测会根据输出动态生成还书、取书、归还和续借请求。因此测试时必须围绕场景链路组织:借阅成功后是否生成正确副本号,副本移动轨迹是否完整;预约送达后保留期是否正确;闭馆后阅读未归还是否扣分;逾期是否只惩罚一次;评分后开馆整理是否把所有副本送到正确书架。状态图和顺序图在这里也参与测试思维,因为它们给出了“哪些迁移应该存在”和“哪些协作路径必须闭合”。

四个单元连起来看,我的测试思维从随机生成输入,走向语义不变量,再走向契约测试,最后走向模型、状态和场景联合验证。

课程收获

这门课给我最大的收获,是让我真正体会到面向对象不是“多写几个类”,而是用对象划分变化。第一单元的变化在文法和代数规则里,第二单元的变化在线程调度和电梯状态里,第三单元的变化在规格和查询复杂度里,第四单元的变化在业务流程、场所状态和信用规则里。每个单元都在逼我回答同一个问题:什么东西应该稳定,什么东西可能变化,变化应该被封装在哪里。

我也更理解了抽象的代价。抽象太少,代码会堆成流程脚本;抽象太多,又会让小需求被过度设计拖慢。第四单元中我保留 Library 作为业务编排中心,同时只把明确会变化或跨流程复用的规则抽成 BorrowLimitCreditPolicyLoanPolicy,就是在这种权衡中做出的选择。

最后,OO 课程也让我对工具保持了更成熟的态度。UML、大模型、本地评测机、JUnit 都不是银弹。它们分别擅长表达结构、辅助思考、扩大测试规模和固定局部契约。真正重要的是让这些工具服务于同一条主线:需求能否被清楚建模,模型能否指导实现,实现能否被追踪和验证。到第四单元结束时,我对“设计”的理解已经从写出能跑的代码,变成了建立一个能解释、能演进、能被检查的系统。

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

309

社区成员

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

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