309
社区成员
发帖
与我相关
我的任务
分享一、第四单元总体回顾
第四单元的三次作业围绕“图书管理系统”展开。与前三个单元相比,本单元最明显的变化是:开发过程不再只是从需求直接写代码,而是要求先完成 UML 建模,再依据模型进行代码实现,也就是实践正向建模与正向开发。
本单元作业的业务对象并不算少,涉及图书、书籍副本、用户、借阅、归还、预约、取书、阅览、信用分、评级、流转轨迹、开馆闭馆整理等多个概念。系统的难点并不主要在算法复杂度,而在于如何把这些业务概念划分为稳定的类,并让类之间的协作关系清晰、可扩展。
从最终代码来看,我的第四单元作业形成了一个以 Library 为业务调度中心,以 BookCopy、User、OrderRequest、AppointmentOffice、GradeManager 等类为核心对象的设计。其中,Library 负责解析操作、调度业务流程和维护全局状态;BookCopy 负责记录单本书副本的状态、位置和流转轨迹;User 负责维护用户的借阅状态、阅读状态、预约状态和信用分;AppointmentOffice 负责预约请求的组织;GradeManager 负责根据评分判断珍藏图书状态。
本单元给我的最大感受是:正向建模并不是在代码之前画一张“形式上的图”,而是通过模型把系统中的对象、职责和关系先显式化。模型如果做得足够认真,后续编码会更像是把模型逐步落地,而不是在代码中临时拼接逻辑。
二、本单元正向建模与开发实践
图书、书籍副本、ISBN、书号;
用户、借阅、归还、预约、取书、阅览;
书架、预约处、借还处、阅览室、用户手中;
开馆、闭馆、整理、移动记录;
信用分、超期、评分、珍藏书架。
这些概念可以分为三类:
第一类是实体对象,例如用户、书籍副本、预约请求。这些对象有自己的状态,并且会随系统运行发生变化。
第二类是场所或资源管理对象,例如书架、预约处、借还处、阅览室。这些对象不一定有复杂行为,但它们代表了书籍流转的空间位置和管理责任。
第三类是服务型或规则型对象,例如评分管理、信用分规则、整理规则。这些对象更关注业务规则,而不是某个具体实体。
在建模时,我需要判断哪些概念应该成为独立类,哪些只适合作为字段或枚举。例如最终代码中,图书副本的位置被抽象为 Location 枚举,而不是为每一个地点都保留复杂类;预约行为则被抽象为 OrderRequest,因为预约请求本身具有用户、ISBN、是否满足、分配副本、过期时间等状态。
最终设计中的主要职责如下:
类/模块 职责
Library 系统总控,处理输入命令,调度借阅、归还、预约、整理等业务
BookCopy 表示一本具体副本,维护编号、ISBN、当前位置、到期日、预约信息、流转轨迹
User 表示用户,维护借阅书籍、当前阅读书籍、当前预约、信用分
OrderRequest 表示一次预约请求,记录请求用户、ISBN、是否已满足、分配副本与过期时间
AppointmentOffice 管理预约请求的生成与预约队列
GradeManager 管理评分并判断某 ISBN 是否为珍藏书
Location 表示书籍所在位置,统一输出短名称
这种划分体现了一种比较典型的面向对象设计思路:状态尽量跟随对象本身,规则通过方法封装,对外暴露较小的接口。比如用户是否已经借了 B 类书、是否已经借了同 ISBN 的 C 类书,这些判断由 User 完成;书籍如何记录位置变化,由 BookCopy 完成;评分如何影响珍藏状态,由 GradeManager 完成。
第一阶段更关注基础借还和预约,类的数量较少,Library 承担较多逻辑;
第二阶段加入阅览室、珍藏书架、图书评分、图书位置流转后,需要加强位置管理和移动记录;
第三阶段加入信用分、续借、逾期、查询信用分等规则后,User 的状态变得更重要,信用分相关逻辑也成为架构中的独立关注点。
因此,正向建模并不是“一次画完”,而是“先抽象出骨架,再在需求扩展中检验模型”。当新增需求能自然地挂到已有类上时,说明模型设计较稳;当新增需求迫使大量修改既有类之间关系时,就说明之前的抽象不够合理。
三、两阶类图在正向建模过程中的作用
本单元特别强调“两阶类图”。我理解的两阶类图并不是简单地画两张类图,而是把系统设计分成两个不同抽象层次:第一阶关注业务概念与领域关系,第二阶关注可实现的代码结构与方法细节。
在图书管理系统中,第一阶类图应该重点呈现:
用户与图书副本之间存在借阅关系;
用户与预约请求之间存在关联;
预约请求最终可能绑定一本具体副本;
图书副本具有位置状态;
系统中存在多个管理场所,例如书架、预约处、借还处、阅览室;
图书可能根据评分进入珍藏书架;
开馆闭馆会触发整理过程。
第一阶类图的价值在于,它帮助我从需求文本中抽离出“稳定概念”。例如“预约”不是一句简单的操作,而是一个有生命周期的对象:创建、等待、被满足、绑定副本、等待取书、过期、完成。只有在第一阶类图中把预约请求建模成对象,后续代码中才不会把预约信息散落在用户、图书和主控类的多个字段里。
例如第一阶类图中可能只有“用户拥有借阅书籍”这一关系,而第二阶类图中则需要具体化为:
User 中维护 Map<String, BookCopy> borrowedBooks;
使用 getBorrowedBCount() 判断 B 类借阅限制;
使用 hasBorrowedC(String isbn) 判断 C 类同 ISBN 借阅限制;
使用 BookCopy readingBook 表示用户当前正在阅览的书;
使用 OrderRequest activeOrder 表示用户当前有效预约;
使用 creditScore 表示信用分。
同样,第一阶类图中“书籍有位置变化”,在第二阶类图中会被具体化为:
BookCopy 保存 Location loc;
moveTo(Location newLoc, LocalDate date) 负责位置变更;
List trace 负责保存移动轨迹;
Location 枚举负责统一位置输出格式。
3. 两阶类图降低了从模型到代码的跳跃
如果只有第一阶类图,模型会比较抽象,编码时仍然需要做大量即时设计;如果一开始就画非常细的实现类图,又容易被尚未确定的代码细节束缚。两阶类图的作用正是在二者之间建立过渡。
第一阶类图帮助我确认“业务边界”,第二阶类图帮助我确认“实现边界”。两者结合后,代码实现时就可以按照如下路径推进:
先根据第一阶类图建立核心类;
再根据第二阶类图补充字段和方法;
编写主流程时,只让 Library 调用各对象提供的接口;
如果发现某个方法需要频繁访问另一个类的内部状态,就反向检查类图中的职责是否划分合理。
对本单元而言,两阶类图最大的意义是避免主类膨胀。图书管理系统的业务流很多,如果直接写代码,很容易形成一个几百行甚至上千行的 Library 类。通过两阶建模,至少可以提前意识到 User、BookCopy、OrderRequest、GradeManager 等对象应该承担自己的状态和行为。
四、本单元作业架构设计总结
书架;
用户手中;
借还处;
预约处。
这一阶段的架构重点是建立“副本”概念,而不是只以 ISBN 表示图书。因为同一个 ISBN 下有多本副本,每一本副本都有独立编号、位置和移动轨迹。若不区分 Book 与 BookCopy,后续处理预约、轨迹查询和移动输出会非常困难。
第一次作业中,我的设计重点包括:
用集合保存所有副本;
为每个用户保存借阅状态;
用预约队列保存等待处理的请求;
在开馆或闭馆时统一执行整理;
每次移动时记录轨迹。
这为后续扩展打下了基础。
这一阶段架构中的关键变化是位置管理变复杂了。书籍的位置不再只是“在书架/不在书架”,而是需要区分:
普通书架 bs;
珍藏书架 tbs;
借还处 bro;
预约处 ao;
阅览室 rr;
用户手中 user。
因此,使用统一的 Location 枚举是比较自然的设计。它避免了在不同类中使用字符串表示位置,也让移动输出更加稳定。
此外,评分机制适合独立为 GradeManager。如果把评分直接写在 Library 中,会让主控类同时承担图书流转和评分统计两种职责;而 GradeManager 可以专门维护每个 ISBN 的评分列表,并提供 isTreasured 这样的判断接口。这样当图书需要在普通书架和珍藏书架之间整理时,Library 只需要询问 GradeManager 即可。
这一阶段最明显的架构压力在 User 类上。因为信用分、借阅限制、当前阅读状态、当前预约状态都和用户直接相关,所以让 User 维护这些状态是合理的。
最终代码中的 User 大致承担了以下职责:
保存用户 ID;
保存已借副本;
判断是否已经借过 B 类或某个 C 类 ISBN;
保存当前正在阅览的书;
保存当前有效预约;
维护信用分并限制上下界;
在借书、取书、归还、阅览、预约时提供状态支持。
信用分规则本身虽然由 Library 中的业务流程触发,但分数的修改封装在 User 的 addCredit 和 reduceCredit 中。这样可以保证信用分上下界约束由 User 自己维护,而不是分散在各个业务流程中。
五、最终代码设计与 UML 模型设计之间的追踪关系
UML 概念 代码实现 追踪关系
图书副本 BookCopy 每本实体书的编号、ISBN、当前位置、借阅到期日、预约信息、移动轨迹
用户 User 用户借阅状态、阅读状态、预约状态、信用分
预约请求 OrderRequest 用户对某 ISBN 的预约,可能绑定一本副本并设置过期时间
预约处 AppointmentOffice 创建预约请求,维护等待队列
评分管理 GradeManager 记录评分,判断是否为珍藏图书
位置 Location 统一表示 bs、tbs、bro、ao、rr、user
图书馆系统 Library 处理命令,组织各对象协作,完成业务流程
这种映射说明代码没有完全偏离模型。模型中的核心实体最终都在代码中有对应实现,且大部分字段和方法也可以回溯到 UML 中的属性和操作。
借书规则;
预约规则;
取书规则;
归还与逾期判断;
阅览与恢复;
续借;
开馆闭馆整理;
信用分扣减时机;
图书移动输出。
这说明最终代码和理想 UML 之间仍存在差距。理想情况下,可以进一步拆分出更多服务类,例如:
BorrowService:处理借书、归还、续借;
OrderService:处理预约、分配、取书、过期;
ArrangeService:处理开馆闭馆整理;
CreditService:统一处理信用分变化;
TraceService:处理轨迹查询和输出。
但是考虑到本单元作业规模、输入输出格式和迭代时间,将这些逻辑集中在 Library 中也有一定现实合理性。它降低了类之间的通信成本,便于快速定位主流程。缺点是随着需求增加,Library 会逐渐变成“上帝类”,后续维护成本上升。
核心实体没有丢失;
副本而非 ISBN 成为流转单位;
用户状态集中维护;
预约请求显式建模;
位置状态统一枚举;
评分管理独立出来。
偏移主要体现在:
部分管理类在代码中被弱化,例如书架、借还处、阅览室没有都作为完整类存在;
业务流程更多集中在 Library,而非由多个 service 或 domain object 协作完成;
一些规则以 if 判断形式存在,尚未抽象成策略或规则对象;
输入输出逻辑和业务逻辑仍有一定耦合。
这种偏移反映了课程作业中的一个常见取舍:UML 模型倾向于表达清晰职责,代码实现则需要在时间、复杂度和测试成本之间折中。正向建模的目标并不是让代码机械地一比一复制 UML,而是让代码的主要结构能被模型解释,并能在需求变化时保持可追踪。
六、使用大模型辅助正向建模的体验与方法总结
在本单元中,使用大模型辅助正向建模给我带来了帮助,但也暴露了一些问题。大模型擅长快速列出类、职责和关系,但在复杂场景中,如果提示不够具体,它很容易给出“看似合理但不可落地”的泛化设计。因此,引导大模型完成架构设计任务,需要把问题拆解得更工程化。
更好的提问方式是先提供约束:
输入输出由课程官方包或固定格式决定;
系统不需要持久化数据库;
重点是模拟书籍副本状态流转;
每个请求必须立即输出 accept/reject;
开馆闭馆会触发统一整理;
必须记录每本副本的移动轨迹;
需要支持后续新增规则。
这样大模型才能围绕具体业务建模,而不是给出过度工程化的框架。
第一步,让大模型从需求中提取名词、动词和状态。
第二步,让大模型区分哪些是实体、哪些是服务、哪些是枚举、哪些只是字段。
第三步,让大模型给出第一阶类图,即领域概念图。
第四步,再让大模型把第一阶类图细化为第二阶类图,补充属性、方法和关联。
第五步,要求大模型检查每条需求是否能映射到某些类和方法上。
这种方式比直接生成代码更可靠。因为大模型生成代码时可能会忽略边界条件,但在建模阶段,如果要求它逐条追踪需求,就更容易发现设计漏洞。
用户已有预约时再次预约;
用户预约的书已经分配但未及时取走;
B 类书只能借一本;
C 类书同 ISBN 不能重复借;
阅览室书闭馆未恢复;
图书评分变化后需要在普通书架和珍藏书架之间移动;
预约书在开馆前和闭馆后分配时过期日期不同;
逾期、按时归还、恢复阅览对信用分的影响不同。
引导大模型时,可以要求它为每个类列出“需要防止的不变量被破坏”。例如:
User 不应同时拥有多个 active order;
BookCopy 同一时刻只能位于一个位置;
被预约处保留的书应有 reservedFor 和 expireDate;
用户取书成功后预约应被清除;
图书移动时必须同步记录 trace。
这种反例检验能够让大模型从“生成设计”转向“审查设计”,效果会更好。
我的体会是,大模型最适合承担以下角色:
帮助梳理候选类;
帮助发现遗漏的状态;
帮助比较不同职责划分;
帮助从需求反推方法;
帮助检查 UML 与代码的一致性;
帮助总结架构演进。
但最终是否采用某个类、是否拆分某个服务、是否引入设计模式,仍然需要由开发者结合代码规模、时间成本和测试难度决定。
七、四个单元中架构设计思维的演进
表达式系统给我的第一个启发是:面向对象设计可以把语法结构自然映射为类结构。比如表达式、项、因子、幂函数、三角函数等对象,都可以对应语法树上的节点。相比把表达式当成字符串反复处理,建立对象结构能够让递归求值、化简、输出更加清晰。
这一单元让我意识到:架构设计的第一步是找到稳定的数据结构。如果数据结构混乱,后面的算法再怎么补丁也会很痛苦。
在这一单元中,我开始关注:
对象之间如何通信;
共享资源如何保护;
线程何时等待、唤醒、结束;
调度策略如何与电梯运行逻辑解耦;
如何避免死锁和忙等。
这一阶段我的架构思维从“类的结构”进一步发展到“对象的生命周期和交互协议”。电梯、请求队列、调度器、输入线程之间不是简单的包含关系,而是并发环境下的协作关系。好的架构不仅要能表达对象,还要能控制对象之间的时序。
在前两个单元中,我更多依赖测试和调试来确认代码正确;而第三单元要求从规格出发理解代码行为。每个方法的前置条件、后置条件、异常行为和对象状态约束都需要被明确描述。
这一单元让我意识到:
方法不是孤立代码片段,而是对外提供的契约;
类的字段必须维护不变量;
复杂系统中,局部正确性比临时调试更可靠;
测试不仅要测输出,也要验证规格覆盖的边界。
这对第四单元也有帮助。第四单元的 UML 建模和第三单元的规格思维是相通的:它们都要求在写代码前先思考系统结构和行为约束。
这一单元让我把之前几个阶段的思维综合起来:
第一单元的数据结构抽象能力;
第二单元的对象协作和流程控制能力;
第三单元的契约、不变量和边界条件意识;
第四单元的正向建模和模型追踪意识。
到第四单元时,我对架构的理解从“把代码分成几个类”变成了“用对象模型表达领域概念,用类之间的关系约束系统演化”。
八、四个单元中测试思维的演进
这一阶段的测试思维主要是:构造足够多的输入,检查输出是否等价。
因此测试重点从“输入输出正确”扩展到了:
高并发请求;
请求到达时间分散;
多电梯协同;
边界楼层;
程序是否能正常结束;
是否出现死锁或长时间无输出。
这一阶段,我开始重视压力测试和多次运行。
例如图结构相关操作中,需要关注:
节点不存在;
边不存在;
重复添加;
连通性变化;
最短路或查询结果边界;
异常触发顺序。
这一阶段的测试思维从“随机覆盖”进一步变成“规格覆盖”。我开始更有意识地检查每个条件分支和异常分支。
本单元中,我认为最重要的测试包括:
一本书从书架到用户,再到借还处,再回到书架;
预约请求从等待到分配,再到取书或过期;
阅览书从书架到阅览室,再恢复到借还处;
珍藏书和普通书架之间的移动;
用户信用分在不同事件中的变化;
开馆前和闭馆后的整理差异;
移动轨迹查询是否完整且顺序正确。
相比前三个单元,第四单元的测试更强调“长序列”。很多错误不会在单条指令中暴露,而是在多天、多次开闭馆、多次借还预约之后出现。因此我逐渐形成了状态驱动测试的意识:不仅检查某次输出,还要检查这次操作之后系统内部状态是否会影响后续行为。
九、课程整体收获
经过 OO 课程四个单元的训练,我最大的收获不是掌握了某个具体语法或框架,而是逐渐形成了面向对象的工程思维。
OO 课程让我理解到,架构设计的目标不是追求形式上的复杂,而是控制复杂度。
封装不是简单地把字段设成 private,而是让对象维护自己的状态和不变量。比如用户信用分的上下界应由 User 自己保证,书籍移动轨迹应由 BookCopy 自己维护。
继承和多态也不是为了使用而使用,而是在存在稳定抽象和多种变体时才有价值。比如第一单元的表达式节点适合用多态表示不同因子;而第四单元中很多业务规则用组合和服务类反而更直接。
过少抽象会导致主类臃肿,过度抽象会导致类太多、调用链太长。好的设计应该让当前需求清晰,同时为合理的未来变化留下空间。
同时,重构也不是“代码写坏了才做”的事情。随着需求迭代,原有设计不可能始终完美,及时重构可以防止小问题积累成大问题。第四单元中,从基础图书流转到信用分和珍藏书规则,正体现了持续调整架构的重要性。
模型和代码之间应该存在追踪关系。模型不一定完全等同于代码,但它应该能够解释代码为什么这样组织。这样的模型才是真正有价值的模型。
十、结语
回顾整个 OO 课程,我经历了从表达式结构、并发协作、规格约束到正向建模的完整训练。第四单元的图书管理系统虽然规模不算特别大,但它综合考察了对象识别、职责划分、状态管理、业务规则建模和模型到代码的追踪。
本单元让我深刻体会到,两阶类图在正向建模中具有重要作用。第一阶类图帮助我理解领域,第二阶类图帮助我落地实现;前者避免设计过早陷入代码细节,后者避免模型停留在空泛概念。两者结合,使得开发过程更有方向,也让最终代码更容易被解释和维护。
同时,使用大模型辅助建模也让我认识到,AI 可以帮助梳理思路、发现遗漏、比较方案,但复杂系统的最终设计仍然需要人来把握。只有当我们能清楚描述需求、约束、边界和设计目标时,大模型才能真正成为有效的架构助手。
总体而言,OO 课程让我从“写程序”逐渐走向“设计系统”。这种思维方式不仅适用于课程作业,也会影响我之后面对更大规模软件工程问题时的分析和实现方式。