308
社区成员
发帖
与我相关
我的任务
分享本技术博客围绕2026年面向对象设计与构造课程的第四单元(图书馆管理系统)三次作业及全课程内容,进行全面的回顾与总结。在第四单元中,我们首次实践了正向建模与开发,通过两阶UML类图指导系统架构设计,并辅以状态图和顺序图描述对象行为与对象间交互;同时,大语言模型在辅助设计、代码生成与错误排查中发挥了重要作用。以下将从六个维度展开:正向建模实践、架构设计与追踪关系、大模型辅助设计体验、架构思维演进、测试思维演进以及课程总体收获。
第四单元的核心训练目标是正向建模——即在编写代码之前,先通过UML类图完成架构设计,再依据设计进行实现。这与前三个单元“先写代码后画图”的逆向工程形成鲜明对比。
在第十四次、第十五次作业中,我们实践了两阶类图的正向建模流程。
一阶类图在代码编写前完成,其核心作用体现在三个方面:
(1)确立核心抽象与职责边界
绘制一阶类图时,必须首先识别系统中的核心实体及其关系。以图书馆管理系统为例,我们需要抽象出 LibraryManager(管理核心)、Book 与 BookCopy(图书与副本)、User(用户)、各种地点类(Bookshelf、AppointmentOffice、BorrowReturnOffice、ReadingRoom)等。这个过程迫使我们思考每个类应当承担的职责——例如,借阅限制逻辑应该放在 User 还是 LibraryManager 中?图书移动轨迹是 BookCopy 的属性还是独立管理?
(2)强制分离关注点
一阶类图不允许我们一开始就陷入实现细节。例如,我们在一阶图中只定义了 LibraryManager 具有 borrowBook、returnBook 等方法,但不关心内部如何实现。这种抽象层次的强制提升,帮助我们建立了更清晰的模块边界。
(3)预判迭代需求
在第十五次作业中,新增了信用分系统、借阅期限和续订功能。由于一阶类图已经建立了良好的基础架构,我们只需在一阶图基础上增加 CreditManager 类和 BorrowRecord 类,并在相关类中补充 credit 属性和 renew 方法即可,而不需要对整体架构推倒重来。
二阶类图在代码完成后进行修正,其核心作用包括:
(1)详细化设计契约
一阶类图中,方法往往只有名称而无参数和返回值。二阶类图要求我们补充完整的参数列表和返回类型——例如 borrowBook 的参数从无到有,变为 (String studentId, ISBN isbn, LocalDate date): BookCopy。这使得类图从“示意”升级为“精确契约”,可以直接指导编码和测试。
(2)检验设计与实现的一致性
评测系统对二阶类图执行R1-R5的全面检查,包括属性名、方法名、可见性、返回值类型的匹配。这一过程揭示了许多设计偏差——例如我们在类图中遗漏了 Main 类的某些辅助方法,或者方法参数顺序与代码不一致。修正这些偏差的过程,本质上是在验证我们的实现是否忠实于原始设计。
(3)管理架构演化
从一阶到二阶,类图的变化(如类名从 Library 改为 LibraryManager、新增 CreditManager 和 BorrowRecord)被记录在重构说明中。这种演化追踪帮助我们理解设计决策的变更原因,也为后续维护提供了历史依据。
关键体会:两阶类图的设计并非“先画图再写代码”那么简单,而是一个螺旋上升的过程——设计指导实现,实现反馈修正设计,最终两者趋于一致。评测系统的相似度检查(要求两阶类图相似度不低于60%)正是为了确保我们没有在一阶时“敷衍画图”而在二阶时“彻底重来”。
本单元作业采用了分层架构 + 领域模型的设计思路:
核心管理层:LibraryManager 作为门面,负责协调所有操作(开馆、闭馆、借阅、归还、预约、取书、阅读、评分、续订、信用查询),持有所有地点对象和用户、图书的集合。
领域模型层:
ISBN(身份标识)、Book(同ISBN的聚合根,管理副本集合)、BookCopy(单个副本,记录状态和移动轨迹)User(管理借阅记录、预约记录、信用分、阅读状态)、BorrowRecord(单次借阅的期限和续订)、OrderRecord(单次预约的过期状态)CreditManager(集中管理所有用户的信用分,支持查询和调整)地点管理层:Bookshelf、TreasuredBookshelf、AppointmentOffice、BorrowReturnOffice、ReadingRoom,各自管理所在位置的图书集合,提供添加、移除、查询功能。
辅助层:Arranger(整理策略)、BorrowLimit(借阅限制检查)、Grader(评分计算)、MoveInfo和MoveTrace(数据传输对象)。
正向建模的核心价值在于保持设计模型与实现代码的可追踪性。以下是几个关键的追踪关系实例:
| 追踪维度 | UML模型 | Java代码 | 一致性检查 |
|---|---|---|---|
| 类名 | LibraryManager | public class LibraryManager | 名称完全匹配 |
| 属性 | bookshelf: Bookshelf | private Bookshelf bookshelf; | 名称、类型、可见性一致 |
| 方法 | borrowBook(studentId, isbn, date): BookCopy | public BookCopy borrowBook(String, ISBN, LocalDate) | 参数顺序、返回值类型一致 |
| 关联关系 | LibraryManager 持有 AppointmentOffice | private AppointmentOffice appointmentOffice; | 属性类型体现关联 |
| 枚举 | Location { BOOKSHELF, ... } | public enum Location { BOOKSHELF, ... } | 枚举项一一对应 |
| 状态图Trigger | @Trigger(from="bs", to="user") | @Trigger(from="bs", to="user") public void borrow() | 状态名称和方法名匹配 |
| 顺序图消息 | orderNewBook: User → LibraryManager | @SendMessage(from="User", to="LibraryManager") public void orderNewBook() | 消息名和方法名一致 |
追踪关系的实践意义:
变更影响分析:当需要修改一个类的方法签名时,可以通过追踪关系快速定位到类图中需要同步更新的部分,以及代码中所有调用该方法的地方。
逆向验证:评测系统的R2-R5检查实际上是在验证“我们最终写的代码是否还是当初设计的样子”。如果偏离过大,说明要么设计不合理,要么实现时丢失了架构约束。
知识传递:类图作为文档,可以帮助其他开发者快速理解系统架构,而不需要通读所有代码。
在本次作业中,我大量使用了大语言模型来辅助完成架构设计和代码实现。以下是基于这一体验总结的有效引导策略:
大模型在开放场景中容易产生幻觉,因此必须在一开始就明确所有硬性约束:
.mdj 文件的正确结构(ownedElements而非children、Model的name字段必须为"Model"、represents必须使用"class.XXX"格式等)。这些细节曾多次导致解析失败,但一旦明确告知,模型就能稳定输出。正向建模本身就是一个逐步细化的过程,这与大模型的能力边界高度契合:
大模型最强的能力之一是基于反馈快速修正。在调试顺序图消息路径时,评测机反复提示“起始消息不存在”,我将错误信息直接提供给模型,模型能够分析出问题在于represents格式、predecessor/successor字段的写法等,并逐次调整直到通过。
有效的反馈方式:
大模型适合完成机械性、规则明确的任务——例如按照规则生成类图的JSON结构、检查方法签名是否匹配、生成符合规范的@Trigger注解。但核心的抽象设计决策——例如将信用管理独立为CreditManager还是放在User中、借阅限制应该由哪个类负责——仍应由人类完成。
在实践中,我会先自己画出架构草图(哪些类、什么关系),然后让模型帮我补充细节、检查一致性、生成规范文件。这种人机协作模式既发挥了人类的抽象思维能力,又利用了大模型对规则和细节的精确处理能力。
回顾整个2026OO课程,我的架构设计思维经历了从“能跑就行”到“设计先行”的显著转变。
第一单元的核心任务是表达式解析与化简。当时的我虽然使用了多个类(Main、Solver、Expr、Polynomial、Term),但本质上仍然是面向过程的思维——数据在类之间流转,类是“函数包”而非真正的对象。Expr类的解析方法长达数百行,圈复杂度爆表,体现了典型的“上帝类”问题。
核心问题:我关注的是“怎么解析字符串”,而不是“如何用对象协作表达语义”。Polynomial和Term虽然封装了数据,但行为逻辑全在Expr中,缺乏真正的职责分配。
第二单元的电梯调度系统迫使我思考层次化设计。我采用了四层架构(输入层→调度层→电梯井层→电梯层),每一层有明确的职责边界。这是第一次真正体会到“高内聚、低耦合”的好处:第六次加检修、第七次加双轿厢,只需修改对应层,整体架构不受影响。
关键收获:我开始理解“线程安全不是加锁越多越好”,而是“控制共享资源的访问顺序”。这为后续理解“不变式”和“契约”埋下了伏笔。
第三单元的JML让我第一次接触到形式化规格。我学会了阅读前置条件、后置条件、不变式,理解了“设计契约”比“实现功能”更重要。在迭代开发中,我发现严格遵循JML的实现往往更简洁、更少bug——因为契约已经帮我排除了大量边界情况。
关键转变:从“写代码再测试”到“先理解契约再编码”。这为第四单元的正向建模奠定了思维基础。
第四单元是前三单元的集大成者:我需要先画UML类图(正向建模),再编写代码,最后通过状态图和顺序图验证设计。两阶类图的实践让我体会到:
最重要的架构思维跃迁:我认识到好的架构不是“写出来的”,而是“设计出来的”。在第十五次作业新增信用分系统时,我能够在一阶类图基础上平滑扩展,而不是像第一单元那样在单个类里堆砌代码。这正是一学期训练的核心成果。
测试方式非常简单粗暴:手工写几个表达式,对比输出。没有自动化测试框架,没有系统的边界覆盖,完全依赖评测机的“公测”和“互测”。Bug发现效率低下,很多隐藏问题直到互测才暴露。
核心缺陷:将“通过评测”等同于“程序正确”。
多线程的调试难度远高于单线程,我学会了用日志定位问题:在关键位置输出时间戳和线程状态,还原并发执行顺序。此外,开始有意识地构造压力测试(大量并发请求)和边界测试(极端等待时间、瞬时请求)。
关键提升:认识到并发程序的正确性不能靠“跑一遍看看”,必须通过多次重复测试和日志分析验证。
这是测试思维的重大转折点。我学会了依据JML规格构造测试用例:
requires的用例,验证异常行为。assertEquals严格检查返回值、状态变化。assignable检查:确认非指定对象未被修改。我还掌握了边界测试的系统化方法:空集合、极值、重复操作、重叠匹配等。
核心理念转变:测试不是为了“证明程序对”,而是为了“发现程序错”。好的测试用例来源于对规格的深入理解,而非随机构造。
第四单元的评测是交互式的:评测机根据程序的输出动态生成后续输入(如还书、取书、续订指令)。这意味着我的程序不仅要正确响应给定输入,还必须保证输出的副本号、移动轨迹等与后续输入一致。
这一阶段我学会了端到端集成测试:
最终形态的测试思维:测试覆盖全流程,关注对象状态的完整生命周期,确保系统在长时间运行中保持一致性。
从第一单元的“上帝类”到第四单元的“两阶类图正向建模”,我完整经历了一次架构设计思维的升级。现在我能自然地问自己:
这些思考已经成为编码前的“肌肉记忆”。
第二单元让我掌握了synchronized、wait/notify、ReentrantLock等核心同步机制,理解了死锁、轮询、可见性等并发陷阱。虽然不是所有人都会经常写多线程代码,但这种对“共享资源有序访问”的理解,对理解分布式系统、数据库事务等高级主题有奠基作用。
第三单元的JML让我认识到:代码之前,先写契约。前置条件和后置条件不仅是文档,更是可执行的需求。这种思维方式让我在第四单元面对复杂业务规则时(如借阅期限、信用分加减分规则),能够更系统地梳理逻辑,而不是边写边改。
第四单元的两阶类图实践,教会了我“先设计再实现”的完整流程。更重要的是,通过R1-R5的检查,我体验了设计模型与代码实现之间的追踪关系——这在工业界是架构评审和代码审查的核心技能。
整个课程中,我深度使用大模型完成了大量工作:生成UML类图JSON、检查方法签名一致性、定位逻辑错误、撰写博客文档。我体会到:
这门课程让我从“写作业的学生”视角切换到“构建系统的工程师”视角。我理解了:
最后:感谢课程组精心设计的四次迭代作业,让我在短短一个学期内,从面向过程的“代码工人”成长为具备架构思维和正向建模能力的“软件工程师”。这门课不仅教会了我Java和UML,更教会了我如何思考、如何设计、如何协作。这些收获将伴随我未来的整个开发生涯。