308
社区成员
发帖
与我相关
我的任务
分享本单元围绕图书馆管理系统展开,从最初的借阅和预约整理到后来加入了阅读室以及评分系统和精品书架,再到最后一次作业的信用分系统。和前三个单元相比,本单元的要求不是让你一上来就写代码,而是先画UML再根据一阶类图去写代码,最后根据写出的代码反向生成UML,包括二阶类图,顺序图以及状态图等等。
两阶类图的机制迫使开发者采用先思考架构再写代码的模式,而不是边写边想。
一阶类图是在写代码前要求设计的,他可能很粗糙,或者与最终代码有着不小的差异。但是他让我们进行了一个先思考再动笔的过程,避免了可能出现的代码写到一半发现不可行,从而重构的危机。评测对于一阶类图也是有着不少的要求的,比如必须包含课程组要求的核心类以及关键词,初看感觉很无语,既然让我们自己设计架构,为什么要对我们的架构有着这么多的约束呢。但是转念一想就明白了,这些约束其实是一种规范,正因为有着核心类的约束,才不会出现所有功能集成到一个类的情况出现,也不会出现核心方法和属性的命名多种多样,让人摸不着头脑的情况。一阶类图至少帮助我们提前确定了主要领域对象和大致关系,例如 Book 表示一个 ISBN 的图书整体,BookCopy 表示具体副本,User 表示用户状态,Order 表示预约记录,各个 Office 和 Shelf 表示图书所在位置。
二阶类图因为是通过代码反向生成的,所以非常接近最终的代码。通过二阶类图与一阶类图的比较,能够暴露出我们一开始没有想到的情况和一些未曾注意到的细节。可以说二阶类图就是对一阶类图的查漏补缺。像是第一次作业的时候,因为赶时间就随便设计了一个一阶类图,连题目都没怎么理解,只是包含了核心类和关键词而已,果不其然,最终评测时,一阶类图与二阶类图的相似度只有惊人的30%。两阶类图的对比能够帮助我们调整设计策略,有助于我们在一开始就设计出接近完美的最终模型,从而在写代码的时候不用再去分心架构的设计问题。
本单元实现了一个带有信用分、借阅期限、预约、阅读、评分等复杂规则的图书馆管理系统。整体架构采用分层集中调度模式,以 LibraryManager 作为核心控制器,协调各个实体类与地点类之间的交互。
| 类名 | 职责 |
|---|---|
Main | 入口,解析输入命令并分发至 LibraryManager |
LibraryManager | 系统核心控制器,处理所有用户请求和整理流程,维护所有图书与用户集合 |
User | 用户实体,管理借阅列表、预约状态、阅读状态、信用分及借阅规则判断 |
Book | 图书种类,管理一类 ISBN 下的所有副本,记录该种类评分 |
BookCopy | 图书副本,记录自身位置、历史轨迹、预约信息、借阅 deadline 等 |
BookShelf | 普通书架(也复用于精品书架),管理架上副本 |
AppointmentOffice | 预约处,管理被预订的副本 |
BorrowAndReturnOffice | 借还处,管理归还/阅读归还暂存的副本 |
ReadRoom | 阅览室,管理正在被阅读的副本及其用户关联 |
TreasureBookShelf | 精品书架(实际复用了 BookShelf,但类图中有单独表示) |
User 仅负责自身状态与规则校验;LibraryManager 负责整体调度。LibraryManager 对外提供统一的 handleBorrow, handleReturn, handleOpen, handleClose 等方法,隐藏内部复杂交互。BookCopy 自身维护 currentLoc 和 history,状态转移通过 moveTo 方法完成,结合 @Trigger 注解标记触发方法。User 的方法(canBorrow, canOrder, canRead 等)中,便于修改和测试。开馆前整理和闭馆后整理统一由 LibraryManager.calculateMoves() 调度,依次执行:信用逾期处理 → 精品书架调整(仅开馆前)→ 预约逾期处理 → 清理借还处和阅览室 → 为预约用户配书。每一步对应一个 @Trigger 注解的私有方法。
类图设计了系统中主要的类、属性、方法及类间关系。关键关系包括:
LibraryManager 关联 User(1 对多)、BookShelf、AppointmentOffice、BorrowAndReturnOffice、ReadRoom、Book 等。Book 与 BookCopy 为一对多组合关系。User 与 BookCopy 之间有关联,表示用户借阅的图书。BookCopy 集合。类图的属性与方法基本覆盖了关键词(borrow, return, order, pick, query, arrange, move, credit 等),保证了语义完整性。
状态图建模了 BookCopy 对象的生命周期。状态包括:Bookshelf, TreasuredBookshelf, ReadingRoom, BorrowAndReturnOffice, AppointmentOffice, User。
每个状态转移对应一个带有 @Trigger 注解的方法,如 handleBorrow()、handleRead()、handleReturn() 等。
Guard 条件根据信用分、评分、是否逾期等规则细化转移分支,例如从 AppointmentOffice 到 TreasuredBookshelf 需要 book.getAveScore() >= 4。
顺序图针对“用户预约新书 → 取书成功”场景进行设计。
生命线包含 :User, :LibraryManager, :AppointmentOffice。
消息序列:orderNewBook → setPendingOrder → 返回确认;随后 getOrderedBook → getReservedCopy → 返回副本 → 取书确认。
代码中通过 @SendMessage 注解标记了相应方法,确保消息名称与代码方法对应。
为了保证正向建模的一致性,课程要求使用注解 @Trigger 和 @SendMessage 建立代码与状态图/顺序图的双向链接。
LibraryManager, User, Book, BookCopy 等)。toHandle 在类图中也存在)。满足覆盖率要求(≥60%)。LibraryManager 中的 users: Map<String, User>)隐式关联,类图中通过 UMLAssociation 显式画出关联线,保证了 R5 校验。Bookshelf, TreasuredBookshelf, ReadingRoom, BorrowAndReturnOffice, AppointmentOffice, User)均与 LibraryBookState 枚举值对应。@Trigger 方法,且 Guard 条件直接来源于代码逻辑(例如 book.getAveScore() >= 4)。InitState 到 Bookshelf 的迁移代表了图书初始化过程。orderNewBook 和结束消息名 getOrderedBook 的硬性要求,在 LibraryManager 中新增了两个包装方法,分别调用原有的 handleOrder 和 handlePick,并加上 @SendMessage 注解。这样既不影响原有功能,又建立了图与代码的精确追踪。通过注解强制双向链接,保证了设计模型与代码实现的同步,使抽象结构在开发中持续有效。
本次作业中我几乎没有使用过大模型,除了第一次作业的时候比较忙,没注意第一阶段作业的截止时间,导致没时间去看题了,迫不得已借助大模型在两个多小时极限完成了一个简略但能通过评测的类图。而事实就是这个类图几乎不可用,可能是因为提示词的原因,大模型在建模过程只是一味迎合评测的要求而没有注意设计的合理性以及写代码的便利,比如有了官方包中的类,他还自顾自地去设计自己的类,以及一些莫名其妙的方法和包含、继承关系,于是我只能将其全部推翻,二阶类图进行了重构。
虽然本单元我使用大模型过少,但还是有一些独到的见解的,不如我个人感觉先让大模型生成相应的代码,再根据代码生成相应的类图可能比直接生成对应的类图要好许多,因为这样大模型在生成类图时会同时兼顾评测的要求以及代码编写的合理性,而不是一味迎合评测的要求,覆盖关键词,从而生成合理的类图(而不像我第一次生成的那坨不可名状之物)。
但是个人感觉利用大模型辅助设计是可取的,但是不要直接让大模型帮你完成所有的工作,像是用大模型总结整个作业的要求,或者生成一些架构,然后你再借鉴这些架构去设计属于你自己的更加合理的架构,对于架构中一些不重要,比较繁琐的方法,也可以让大模型进行代工,但是不可将核心方法交给大模型去编写。同时编写完代码重新设计二阶类图时,可以让大模型,提取属性和方法改成可以直接输入starUML的形式,避免一个一个去敲所浪费的时间。
最后可以让大模型做一些测试工作,像是评判一下你的类图设计的合理性,并给出理由,以及给出类图和代码,让大模型据此去找代码里的潜在bug等等。
至于提示词方面,我觉得可以用一个大模型去根据指导书生成精简的提示词,然后再借助另一个大模型去根据提示词完成相应的工作。
最后的最后,针对直接让大模型生成.mdj文件而不是自己去画类图的情况,虽然.mdj文件是json文件,直接生成并不困难,但是大模型生成的.mdj文件一般都打不开,或者打开了starUML也识别不了大模型生成的部分,虽然直接生成.mdj文件通过评测是可行的(亲身实验过)但是这种方式产生的不仅不利用你日后对类图的调试,同时你自己也看不到类图,最后大模型生成的类图多多少少都会有像是定位错位等小问题,而要改的话,又要生成.mdj文件,不仅效率低而且耗token,且学不到任何东西,实在不建议。
回顾整个2026 OO课程,四个单元的架构设计呈现出一个层层递进的趋势。
第一单元主要教了我们如何进行分层设计和语法解耦。它通过语法解析的形式向我们展示如何很好的处理嵌套关系,比如解析句子到解析词元再到解析具体的字符。同样我们也可以将语法解析的思路用到对象层面中去,比如我们也可以利用这样的分层化思想去解析对象的设计,通过一个对象处理输入逻辑,然后调用另一个对象去处理每个输入,最后将每个输入的具体处理分发在不同的对象或方法里,实现嵌套调用,但是不会过多耦合。
第二单元主要教了我们怎么处理多线程并发的问题,并教会了我们设计状态来处理问题。这一单元我们知道了如何通过各种锁和同步来处理线程共享资源的问题,但是最重要的是线程间传递的是电梯的状态,且电梯同时也可能处于回收,检修、升级、单轿厢和双轿厢等多种状态。如何传递电梯的这些状态,并根据对应的状态进行相应的处理就成为了本单元的难点。通过这一单元的学习,我掌握了状态转移逻辑的设计,以及不同线程间状态的传递等。
第三单元主要教了我们怎样进行规格化设计。这一单元围绕JML 展开,其核心变化是从"我设计接口"转向"接口由规格给出"。架构设计的自由度被大幅压缩——类名、方法签名、异常类型全部由 JML 锁定。这一单元教会了我一件事:当接口被外部规格锁死时,架构设计的重心从"定义接口"转向了"优化实现"——选择 HashMap 还是 ArrayList、缓存哪些中间结果、如何组织内部辅助方法——这些是规格不限制的部分,也是人与人之间性能差异的来源。
第四单元主要教了我们如何设计UML架构。这一单元围绕UML展开,与其他单元相比,其核心变化是从反向建模变成了正向建模。要求我们在写代码前考虑架构的设计同时设计状态图和顺序图等更要求我们对于自己的架构的掌握程度进一步地提高。这一单元与别的单元不同,其重心不在于杰出的性能,而是对于架构的掌握和熟悉。通过这一单元的学习,我养成了在编写代码时先进行架构设计的良好习惯,避免了不必要的重构,提高了开发效率。
这四个单元分别教会了我:分层设计架构,架构即是代码,模型约束代码,架构先于设计。
第一单元:从样例到黑盒对拍,重视数学等价性
初始阶段依赖手写样例,但很快意识到表达式求值这类问题更适合随机生成与对拍验证——用 Python/SymPy 等工具构造表达式,将程序输出与可靠数学结果对比。这一单元的核心是“输出是否数学等价”,但两个例子都指出,随机测试难以覆盖深层嵌套、短路逻辑或公因式提取等极端场景,真正有效的测试往往源于对可能性能缺陷或架构漏洞的预判。因此,测试思维从“样例驱动”扩展为“随机+极端数据驱动”,并开始思考测试的杀伤力。
第二单元:从结果验证转向过程合法性检查
多线程电梯系统迫使我关注并发行为而非最终结果。由于并发 bug 常不稳定复现,简单的单次运行不足以证明正确性,必须依赖日志检测器、随机请求生成和场景构造(如高峰请求、跨楼层、检修、双轿厢等),逐条检查时间、载重、开关门、请求是否丢失或重复接送等过程约束。这个阶段,测试的核心从“输出对不对”变成“执行过程是否合法”,也让我认识到对于并发系统,中间行为与最终结果同等重要,甚至更能暴露设计缺陷。
第三单元:围绕规格进行全覆盖,兼顾性能测试
JML 规范让异常条件和正常路径清晰可辨,因此测试开始按正常路径、异常路径、边界值拆分,并与规格逐条对应(如用户不存在、重复关注、硬币不足、评论编号重复等)。两个例子都强调,代码分支覆盖不等于规格覆盖,必须检验 ensures、pure 方法、assignable 限制等是否符合规格。同时,性能测试在这一阶段凸显——即使逻辑正确,若最短路缓存、贡献者 O(1) 维护、推荐堆等优化不到位,仍可能超时。测试思维由此从“功能覆盖”过渡到“规格覆盖 + 性能压力”。
第四单元:模型驱动的场景与状态一致性测试
图书馆系统的复杂性体现在跨日期、跨状态的连续序列中,错误常隐藏于长期交互而非单个请求。因此测试必须从状态图和顺序图反推用例:每条状态转移至少构造一次,每个拒绝条件至少覆盖一次,每个日期边界单独测试,并验证预约、借还、整理、续借、扣分等流程是否符合模型预期。这一阶段,测试思维进一步上升为“模型一致性检查”,不仅验证输入输出,更要追踪图书副本状态、信用分变化、预约到期处理等,确保代码实现与设计模型完全对应。
总结:测试思维的多维跃迁
四个单元下来,我的测试思维经历了四次关键跃迁:
驱动源:从样例 → 随机对拍 → 规格拆分 → 模型推导;
验证目标:从结果是否正确 → 过程是否合法 → 规格是否满足 → 状态是否一致;
测试设计:从直觉构造 → 自动化生成 → 规格映射 → 状态机遍历。
最终,测试不再是代码完工后的补救措施,而是贯穿开发全程的思维工具——它帮助我提前暴露架构不清、需求遗漏和设计矛盾,也让代码的健壮性和可维护性在迭代中真正得到保障。这种演进不仅提升了调试效率,更深刻改变了我对“正确性”的理解:正确不是某个时刻的输出,而是系统在所有合法行为序列下的持续合规。
OO课程让我从“以函数为中心”转向“以设计为中心”。
最大收获是边界意识——Unit1的Factor接口和Unit2的无状态策略证明,好的设计先划清职责边界,再填充实现。契约思维(JML、UML)不是负担,而是强制回答“谁改状态”和“状态如何转移”的导航线。
测试与设计互为镜像,难测的代码往往架构有问题;第四单元的模型驱动测试让我学会从状态图反推用例,确保状态转移全覆盖。
AI辅助编程中,Agent是放大镜而非替代者,能提升检查效率,但架构决策必须由自己负责。
迭代中,可扩展性比首次通过更重要,需求增长应局部修改而非推翻重来。
最终,我完成了从“为了通过评测写代码”到“为了控制复杂度做设计”的转变,这是OO课程最有价值的地方。