309
社区成员
发帖
与我相关
我的任务
分享第四单元围绕图书馆管理系统展开,三次作业逐步扩展需求:
这一单元和前三个单元最大的区别是:代码不是唯一目标,UML 模型本身也成为设计和评测的一部分。开发过程需要从需求中抽取对象和关系,先完成一阶类图,再根据模型写代码,最后用最终代码修正二阶类图,并通过状态图、顺序图表达动态行为。
| 需求内容 | 代码设计 | 说明 |
|---|---|---|
| 图书种类 | Book | 表示某一种 ISBN 图书,保存类别、副本和评分 |
| 具体副本 | BookCopy | 表示一本实际可移动的书,保存位置、持有者、预约者和轨迹 |
| 用户 | User | 保存借阅、阅读、预约、信用分和借阅记录 |
| 普通书架 | BookShelf | 管理普通书架上的副本 |
| 珍本书架 | TreasuredBookShelf | 管理评分达到珍本标准的副本 |
| 借还处 | BorrowAndReturnOffice | 接收还书、阅读归还和续借相关操作 |
| 预约处 | AppointmentOffice | 管理预约记录和已经送达的预留副本 |
| 阅览室 | ReadingRoom | 管理用户正在阅读的副本 |
| 借阅记录 | LoanRecord | 记录借阅日期、到期日期、续借天数和逾期扣分状态 |
| 预约记录 | Reservation | 记录预约用户、ISBN、绑定副本、到期时间和是否扣分 |
| 移动轨迹 | MovingTrace | 记录副本从一个位置到另一个位置的过程 |
| 规则判断 | LimitPolicy | 统一判断借阅、预约、阅读、归还、续借是否合法 |
| 整理流程 | ArrangementService | 处理开馆前、闭馆后的副本移动 |
其中最关键的抽象是 Book 和 BookCopy 的区分。Book 对应 ISBN 层面的图书,BookCopy 对应某一本具体副本。图书馆系统真正发生移动、借出、归还、预约送达和查询轨迹的是具体副本,因此移动轨迹必须绑定到 BookCopy,不能只绑定到 ISBN。
一阶类图的作用是给编码前的架构定方向,主要解决三个问题:
系统有哪些核心类 :LibraryManager、Book、BookCopy、User、BookShelf、AppointmentOffice、BorrowAndReturnOffice 等。
类之间有哪些基本关系 : LibraryManager 管理用户、图书、副本和各场所;Book 包含多个 BookCopy;User 持有借阅、阅读和预约状态。
哪些逻辑不能塞进 Main :Main 只负责输入输出适配,业务调度由 LibraryManager 完成,规则判断由 LimitPolicy 完成,副本位置管理由不同场所类完成。
第十三次作业中,借书流程由 LibraryManager.borrowBook() 调度:先取得用户和图书,再调用 LimitPolicy.canBorrow() 判断限制,之后从书架取出可用 BookCopy,更新用户状态、副本位置和移动轨迹。
根据最终代码修正模型。实现过程中,如果新增的类确实承担了业务职责,应进入二阶类图。
ReadingRoom 成为副本可能停留的位置,二阶类图和状态图都需要体现。Book 中出现评分字段,TreasuredBookShelf 成为新的场所类。User 保存已借副本已经不够,需要 LoanRecord 记录 borrowDate、dueDate、renewedDays、returned、overduePenaltyApplied 等信息。User 中的 creditScore 和 LimitPolicy 中的信用判断都需要反映到模型中。因此,两阶类图之间的关系可以概括为:
| 阶段 | 作用 | 重点 |
|---|---|---|
| 一阶类图 | 建立设计假设 | 从需求中抽取核心对象和关系 |
| 代码实现 | 验证设计是否可用 | 暴露职责过重、状态不足、关系缺失等问题 |
| 二阶类图 | 固化最终结构 | 与最终代码中的类、字段、方法和关联保持一致 |
如果没有一阶类图,图书馆系统很容易写成一个巨大的命令分支:读入命令、判断规则、修改副本位置、输出结果全部写在一起。这样第十三次可能还能通过,但第十四、十五次新增状态和规则后会难以维护。
一阶类图至少提前固定了几个基本事实:
这些设计不一定一开始完全准确,但它们保证了代码从一开始就有对象边界。
二阶类图更接近“实现后的追踪表”,主要回答:
List、Map 或对象引用;BookLocation;在第十五次作业的代码中,User 只保存信用分还不够,还要能通过 LoanRecord 追踪某次借阅是否到期、是否续借、是否扣过逾期分。如果二阶类图只保留 User 与 BookCopy 的关系,而没有体现 LoanRecord,模型就不能解释续借和逾期扣分的实现。
二阶类图不能只追求和一阶类图相似,应该保留一阶类图的主干,同时把实现过程中合理新增的类和关系补进去。
第十三次作业的核心是基础借阅流程,包括借书、还书、预约、取书、查询和整理。
| 模块 | 代表类 | 职责 |
|---|---|---|
| 输入解析 | Main、CommandParser、各类 Request | 把输入转换为请求对象 |
| 统一调度 | LibraryManager | 处理用户命令和开闭馆流程 |
| 图书模型 | Book、BookCopy、Isbn | 区分图书种类和具体副本 |
| 用户模型 | User | 保存借阅和预约状态 |
| 场所模型 | BookShelf、AppointmentOffice、BorrowAndReturnOffice | 管理副本当前位置 |
| 规则判断 | LimitPolicy | 判断能否借、约、取 |
| 移动记录 | MovingTrace | 保存副本流转路径 |
| 整理服务 | ArrangementService | 处理开馆前、闭馆后的副本移动 |
第十三次最重要的设计是围绕 BookCopy 的流转建立系统:
BookShelf 移动到 User;User 移动到 BorrowAndReturnOffice;AppointmentOffice;AppointmentOffice 移动到 User;MovingTrace 输出具体副本的移动记录。这一阶段的架构重点是把“副本位置变化”作为主线,而不是只围绕输入命令写代码。
第十四次新增阅读、归还阅读书籍、评分和珍本书架。表面上只是多了几个命令,实际改变的是 BookCopy 的状态空间。
| 新需求 | 代码修改 | 作用 |
|---|---|---|
| 阅读 | 增加 ReadingRoom、ReadRequest、RestoreRequest | 区分阅读和借阅 |
| 阅读归还 | 扩展 User、ReadingRoom、BorrowAndReturnOffice | 支持从阅览室回到借还处 |
| 评分 | 增加 GradeRequest,Book 保存 scoreSum、scoreCount | 影响是否进入珍本书架 |
| 珍本书架 | 增加 TreasuredBookShelf | 高评分图书进入新的场所 |
| 预约记录 | 增加 Reservation | 保存预约、绑定副本、过期状态 |
| 状态图 | 扩展 BookLocation | 表示普通书架、珍本书架、阅览室等状态 |
第十三次中,副本主要在普通书架、用户、预约处、借还处之间移动。第十四次之后,副本可能处于:
BOOKSHELFTREASURED_BOOKSHELFREADING_ROOMBORROW_AND_RETURN_OFFICEAPPOINTMENT_OFFICEUSER这说明状态图最适合围绕 BookCopy 绘制,因为 BookCopy.location 是贯穿全部业务流程的核心状态。
评分功能也改变了整理逻辑。Book.addScore() 更新评分,Book.getAverageScore() 和 Book.isTreasured() 判断是否为珍本。整理时,ArrangementService.chooseShelfByScore() 根据评分决定副本进入普通书架还是珍本书架。评分不只是一个输出操作,而是会影响之后的状态迁移。
第十五次新增信用分、借阅期限、续借和信用查询。这个阶段的重点不再只是副本当前位置,而是跨日期、跨流程的用户状态和借阅记录。
| 新需求 | 代码修改 | 作用 |
|---|---|---|
| 信用分 | User 增加 creditScore | 保存用户信用状态 |
| 信用查询 | 增加 CreditQueryRequest | 查询指定用户信用分 |
| 借阅期限 | 增加 LoanRecord | 记录借出日期、到期日期、是否归还 |
| 续借 | 增加 RenewRequest,扩展 LimitPolicy.canRenew() | 判断续借合法性并更新到期时间 |
| 逾期处理 | LoanRecord.applyOverduePenalty() | 防止重复扣分 |
| 预约过期扣分 | Reservation.deductCreditIfExpired() | 处理预约后不取的信用惩罚 |
| 官方接口适配 | Main 调用官方接口,业务仍交给 LibraryManager | 降低输入输出变化对业务层的影响 |
第十五次最关键的新增类是 LoanRecord。在前两次作业中,User 保存已借副本基本够用;但加入期限和续借后,必须记录一次借阅行为的细节:
因此,LoanRecord 不是为了增加类数量,而是因为“借阅行为”本身已经成为一个需要长期保存状态的对象。
信用分也改变了规则判断。User 保存 creditScore,LimitPolicy 负责判断信用是否满足借阅、预约、阅读和续借要求。这样,信用规则不会散落在 borrowBook()、orderBook()、readBook()、renewBook() 等方法里。
| UML 类 | 代码职责 |
|---|---|
LibraryManager | 系统业务调度中心,管理用户、图书、副本和各类场所 |
Book | ISBN 层面的图书,保存类别、副本和评分 |
BookCopy | 具体副本,保存位置、持有者、预约者和移动轨迹 |
User | 用户状态,包括借阅、阅读、预约和信用分 |
BookShelf | 普通书架 |
TreasuredBookShelf | 珍本书架 |
BorrowAndReturnOffice | 借还处 |
AppointmentOffice | 预约处 |
ReadingRoom | 阅览室 |
Reservation | 预约记录 |
LoanRecord | 借阅记录 |
MovingTrace | 移动轨迹 |
LimitPolicy | 规则判断 |
ArrangementService | 开闭馆整理 |
如果代码中新增了核心类,二阶类图中应补充;如果 UML 中某个类在代码中没有实际职责,就应删除或合并。
| 类 | 关键字段 |
|---|---|
LibraryManager | users、books、copies、各场所对象、arrangementService |
Book | isbn、category、scoreSum、scoreCount |
BookCopy | copyCode、isbn、book、location、holderUserId、reservedUserId、reservedDate、expiredDate |
User | userId、creditScore、借阅/阅读/预约相关集合 |
Reservation | userId、isbn、copy、reserveDate、expireDate、active、creditDeducted |
LoanRecord | userId、copy、borrowDate、dueDate、renewedDays、returned、overduePenaltyApplied |
MovingTrace | date、sequenceNumber、copyCode、from、to、userId |
这些字段都不是装饰性属性,而是直接参与业务判断或输出。
| UML 关联 | 代码依据 |
|---|---|
LibraryManager 管理多个 User | Map<String, User> |
LibraryManager 管理多个 Book | Map<Isbn, Book> |
LibraryManager 管理多个 BookCopy | Map<String, BookCopy> |
Book 对应多个 BookCopy | Book 中维护副本集合 |
User 关联借阅、阅读、预约状态 | User 中维护相关集合和记录 |
Reservation 绑定 BookCopy | Reservation.copy |
LoanRecord 绑定 BookCopy | LoanRecord.copy |
BookCopy 对应多条 MovingTrace | 副本移动时持续追加轨迹 |
二阶类图中的关系应当能在字段、集合或明确的管理关系中找到依据,不能只凭语义感觉添加。
| 业务 | 代码方法 |
|---|---|
| 借书 | borrowBook() |
| 还书 | returnBook() |
| 预约 | orderBook() |
| 取书 | pickBook() |
| 阅读 | readBook() |
| 归还阅读书 | restoreBook() |
| 评分 | gradeBook() |
| 续借 | renewBook() |
| 信用查询 | CreditQueryRequest 对应处理分支 |
| 查询轨迹 | queryCommand() / queryMovingTrace() |
| 开闭馆整理 | openLibrary()、closeLibrary()、ArrangementService |
顺序图要体现一次业务请求中对象之间的调用关系。例如预约流程中,LibraryManager 需要调用 LimitPolicy 判断规则,更新 User 的预约状态,再通过 AppointmentOffice 或 Reservation 记录预约信息。顺序图如果只画“用户调用系统”,就不能反映真实对象协作。
BookCopy.location 的追踪状态图的核心对象是 BookCopy。它的位置状态对应 BookLocation:
状态迁移对应具体业务操作,例如借书、还书、预约送达、取书、阅读、阅读归还、整理。状态图的价值在于检查是否出现不合理迁移。例如,副本从用户手中归还后不应直接回到书架,而应先进入借还处,再由整理流程移动到普通书架或珍本书架。
本单元中,我主要把大模型作为已有模型和代码的检查工具,比较有效的用法有以下几种。
作业新增需求,对照已有类图和代码检查遗漏。例如:
ReadingRoom、TreasuredBookShelf、Reservation;LoanRecord、RenewRequest、CreditQueryRequest;BookLocation 是否覆盖新增状态;User 是否体现信用分和长期状态。LibraryManager 作为总控类可以承担调度职责,但不应把所有逻辑都写进去。大模型可以帮助分析哪些逻辑应该拆出:
| 逻辑 | 更合适的位置 |
|---|---|
| 借阅、预约、阅读、续借限制 | LimitPolicy |
| 副本位置变化 | BookCopy 和各场所类 |
| 开闭馆整理 | ArrangementService |
| 预约到期和扣分 | Reservation |
| 借阅期限、续借、逾期扣分 | LoanRecord |
| 信用分数值保存 | User |
避免 LibraryManager 变成无边界的大类。
二阶类图需要和最终代码一致,但也要能说明从一阶类图演进而来。比较有用的检查包括:
例如 LoanRecord 不一定在第十三次就出现,但第十五次加入期限和续借后,它就成为必要对象。这样二阶类图补充它是合理演进,而不是随意增加类。
比起笼统问“类图合不合理”,更有效的问题是:
List 或 Map;BookLocation;config.yml 中路径是否指向正确的 UML 文件。这种提问方式能把大模型限制在真实代码和真实作业要求之内,避免得到表面完整但无法提交的答案。
第一单元主要处理表达式解析、化简和求导。架构重点是把表达式拆成有层次的对象,例如表达式、项、因子、幂函数、三角函数、自定义函数等。不同对象负责自己的解析、求导和输出。这样做的好处是,新增一种因子或函数时,不需要重写整个表达式处理流程。
第一单元让我形成的基本认识是:面向对象首先是把复杂结构拆成有职责的对象。
第二单元的电梯作业引入了多线程。相比第一单元,困难不再只是数据结构和算法,而是运行时对象如何协作。需要解决的问题变成了:
第二单元让我认识到,架构不只包括类之间的静态关系,还包括运行时通信协议和共享状态边界。一个对象能不能直接修改另一个对象的状态、哪些数据需要加锁、什么时候通知线程,都属于架构设计的一部分。
第三单元围绕 JML 规格展开。这个单元的重点是理解规格,并让实现满足 requires、ensures、assignable、异常行为等约束。这一单元更重视方法边界:
第三单元让我认识到,面向对象设计不只是类划分,还包括方法行为契约。代码能运行不代表正确,只有满足规格描述才算正确。
第四单元进一步要求从 UML 模型出发设计系统。一阶类图要求编码前先描述对象结构,二阶类图要求编码后保持模型和代码一致,状态图和顺序图要求表达对象生命周期和协作路径。
这一单元让我对架构的理解从“代码文件如何组织”扩展到“需求、模型、代码如何互相对应”。一个概念到底应该设计成类、字段、枚举、服务类、状态还是消息,需要结合需求稳定性和代码职责判断。
第一单元的测试主要围绕表达式展开。测试重点包括:
这一阶段的测试主要是样例、手写边界数据和对拍,目标是保证输出表达式在数学意义上正确。
第二单元的测试难度明显增加。电梯系统不是只看最终输出,还要检查运行过程是否合法。测试重点包括:
这一阶段让我认识到,多线程 bug 不一定稳定复现,因此需要大量随机数据、压力测试和日志检查。
第三单元的测试围绕 JML 规格展开。相比前两个单元,测试不再只是看最终输出,而是要检查每个方法是否满足规格。测试重点包括:
ensures;这一阶段我开始更重视 JUnit 和针对方法的单元测试。规格越明确,测试就越能拆成具体断言。
第四单元的测试对象不仅是代码,还有 UML 模型和提交结构。测试重点包括:
BookCopy.location 的状态变化是否符合状态图;config.yml、uml_pre.mdj、uml_ultimate.mdj 等文件路径是否正确;这一单元让我认识到,工程作业中的“正确”不只包括程序输出,还包括模型、配置、注解、文件结构和评测接口。
课程开始时,我对面向对象的理解比较停留在“写类、封装字段、调用方法”。完成四个单元后,我更能理解类存在的依据:一个类应该对应稳定职责,而不是为了拆代码而拆代码。
例如第四单元中,Book 和 BookCopy 的区分就是稳定职责的体现;LoanRecord 的出现是因为借阅行为本身需要保存长期状态;LimitPolicy 的存在是为了集中管理规则,避免判断散落在各个业务流程中。
四个单元的作业都有增量迭代。前一次作业的结构如果设计过死,下一次新增功能时就会很难改。第四单元表现得最明显:第十三次的基础架构如果没有保留场所类和副本状态,后面加入阅览室、珍本书架、信用分和续借时会很难扩展。
因此我现在更倾向于先找稳定对象和变化点。稳定对象保持清晰职责,变化点通过服务类、策略类或记录类承接。
大模型可以提高效率,但不能代替自己的设计判断。比较适合让它做:
但最终是否修改类图、是否新增类、是否调整代码,仍然要回到指导书、评测规则和自己的实现。
回顾整个 OO 课程,四个单元分别训练了对象分层、并发协作、规格约束和模型先行。从一开始只关注“能不能写出来”,逐渐转向关注“结构是否清楚、职责是否明确、状态是否一致、模型是否能解释代码”。这也是这门课对我最核心的训练。