BUAA OO Unit4

夏雨蔷-24371025 2026-06-20 11:38:56

第四单元技术博客:小型图书馆管理系统的正向建模与迭代开发

一、正向建模与开发

正向建模

正向建模是指在编码实现之前,先通过UML等建模语言完成系统设计,再依据模型指导编码实现的开发方式。本单元的三次作业严格遵循"设计程序架构 → 绘制UML类图 → 编程实现"的工作流,是对正向建模能力的一次完整训练。

1.2 两阶类图的实践路径

本单元的类图设计并非一蹴而就,而是经历了一个"两阶段"的迭代过程:

第一阶段:需求驱动的初步设计

在拿到作业要求后,首先根据需求描述抽象出核心概念:

需求概念对应类职责
图书馆Library系统的中枢控制器,协调所有子模块
书架BookShelf管理图书的库存与副本
借还处BorrowReturnOffice暂存归还的图书
预约处AppointmentOffice管理预约请求与预留图书
学生/用户Student维护用户的借阅状态与限制
图书Book图书领域对象(书号+预约日期)
阅览室ReadingRoom (v2新增)管理当日阅读中的图书
评分状态BookStatus (v2新增)跟踪ISBN维度的评分与精品状态
借阅期限BorrowLimit (v3新增)封装借阅期限计算与逾期判定

这一阶段重点解决的是**"有哪些类"和"类之间如何关联"**的问题。比如Library组合了BookShelf、AppointmentOffice、BorrowReturnOffice,而Book作为值对象在这些模块间流动——这些关系必须在开工前就确定下来。

第二阶段:实现反馈的迭代修正

在编码过程中,会发现初步设计中存在不合理的部分,需要回头修正类图:

  • HW13中,Student的borrowBookB字段最初设计为LibraryBookId,而borrowBookC是一个HashSet<LibraryBookId>。这个设计一直沿用到HW14。
  • HW15引入借阅期限后,borrowBookB需要从简单的LibraryBookId升级为主持借阅期限信息的BorrowLimit对象,而borrowBookC也变成了HashMap<LibraryBookId, BorrowLimit>。这一改动涉及Student、Library、BorrowLimit三个类的协同变更,类图的变更是必不可少的导航工具。
  • HW14引入精品书架后,BookShelf需要区分普通书架和精品书架,原设计被修改为添加type字段(0=普通,1=精品),并新增getBookShelf(isbn)方法来动态路由。

1.3 类图在正向建模中的三个关键作用

作用一:职责的可视化分派

面对一个包含借书、还书、预约、取书、评分、阅读、续订等十余种操作的复杂系统,将所有逻辑塞进一个"上帝类"是最容易的陷阱。类图强制你思考:这个操作应该由哪个类来执行? 例如:

  • Library负责流程编排(openArrangeBooks),但不直接管理图书副本——这交给BookShelf
  • BookShelf负责副本的增删查,但不关心借阅规则——这由Student.canBorrow()判定
  • BorrowLimit封装逾期计算,使得Student和Library都不需要直接操作日期运算

这种"高内聚、低耦合"的分层在类图中一目了然。

作用二:增量开发

三次作业的功能叠加是递增的:

HW13: 借书/还书/预约/取书/查询(4种位置状态)
  └→ HW14: +阅读/归还/评分/精品书架(6种位置状态)
      └→ HW15: +信用分/借阅期限/续订(6种位置+信用维度)

每次迭代都需要修改多处代码。类图在这里有着重要作用——通过查看类图中被修改类的关联关系,可以预判哪些类会受到波及。例如HW15新增信用分系统时,类图告诉我们:Student持有credit字段,Library中所有涉及借阅、预约、阅读、逾期处理的方法都需要检查信用分,而BookShelf和BorrowReturnOffice则完全不需要改动——这就是类图的导航价值。

作用三:UML到代码的双向一致性

类图不是画完就束之高阁的装饰品。本单元要求类图与最终代码保持一致,这意味着编码过程中的每一次调整都需要回写到图里。这种"图即文档、文档即图"的实践,建立在代码与UML之间可追溯的基础上(详见第二部分)。


二、架构设计总结与UML

2.1 最终架构全景

经过三次迭代,最终的小型图书馆管理系统形成了以下架构:

Main (入口,命令分发)
  └── Library (中央控制器)
        ├── BookShelf (普通书架) ── 管理副本库存
        ├── BookShelf (精品书架) ── 管理精品书副本
        ├── BorrowReturnOffice  ── 暂存归还图书
        ├── AppointmentOffice   ── 管理预约与预留
        ├── ReadingRoom         ── 管理阅读中的图书
        ├── HashMap<isbn, BookStatus>  ── 按ISBN维护评分状态
        ├── HashMap<string, Student>   ── 按学号维护用户状态
        │     ├── BorrowLimit (B类书借阅期限)
        │     └── HashMap<id, BorrowLimit> (C类书借阅期限)
        └── HashMap<id, List<Trace>>    ── 图书移动轨迹日志

这是一个典型的中心调度+功能模块架构。Library作为Facade门面,对外暴露所有业务接口(borrowBook、returnBook、orderBook、pickBook、readBook、restoreBook、gradedBook、queryBook、renewBook、queryCredit),对内协调各功能模块完成任务。每个功能模块是自治的——它们持有自己的数据并对数据的一致性负责。

2.2 设计模式与原则的应用

虽然没有显式使用GoF设计模式,但代码中自然体现了多种设计原则:

  • 单一职责原则(SRP):每个类有明确的单一职责。BookShelf只管理库存,BookStatus只维护评分,BorrowLimit只计算期限。
  • 信息专家模式:谁拥有数据谁负责处理。BookStatus.isChange()判断精品状态变化,BorrowLimit.isOverdue()判断逾期——数据和行为在同一类中。
  • 控制反转Main调度LibraryLibrary调度各模块,模块之间不直接通信,由上层协调。

2.3 UML类图与代码的追踪关系

这是本单元的核心考察点之一:UML模型与代码实现之间必须建立可验证的追踪链

类级别的追踪(1:1映射)

每一个UML类在代码中对应一个.java源文件:

UML类图元素源代码文件备注
MainMain.java入口类(3个版本均有)
LibraryLibrary.java核心控制器(逐步膨胀)
BookShelfBookShelf.javav1起存在,v2增加type字段
BookBook.java值对象(3个版本未变)
StudentStudent.javav3从简单持有升级为带信用分
BorrowReturnOfficeBorrowReturnOffice.java3个版本基本未变
AppointmentOfficeAppointmentOffice.java3个版本基本未变
BookStatusBookStatus.javav2新增,v3沿用
ReadingRoomReadingRoom.javav2新增,v3沿用
BorrowLimitBorrowLimit.javav3新增

属性级别的追踪

UML中每一个属性字段在代码中有对应声明。以Student的HW15版本为例:

UML属性代码声明类型
studentIdprivate String studentIdString
borrowBookBprivate LibraryBookId borrowBookBLibraryBookId
borrowDateBprivate BorrowLimit borrowDateBBorrowLimit
borrowBookCprivate HashMap<LibraryBookId, BorrowLimit> borrowBookCHashMap
creditprivate int credit (初始值100)int

方法级别的追踪

UML中的每个公共方法在代码中有对应实现:

UML方法 (Library)代码方法@Trigger注解
borrowBook()public void borrowBook(LibraryReqCmd)BOOKSHELF→USER
returnBook()public void returnBook(LibraryReqCmd)USER→BRO
orderBook()public void orderBook(LibraryReqCmd)-
pickBook()public void pickBook(LibraryReqCmd)AO→USER
readBook()public void readBook(LibraryReqCmd)SHELF→READING_ROOM
restoreBook()public void restoreBook(LibraryReqCmd)READING_ROOM→BRO
gradedBook()public void gradedBook(LibraryReqCmd)-
renewBook()public void renewBook(LibraryReqCmd) (v3新增)-
queryCredit()public void queryCredit(LibraryQcsCmd) (v3新增)-

这表示无论从普通书架还是精品书架借书,最终都会转移到USER状态——这与状态图中的转移定义完全一致。

追踪关系的一致性问题

在对比UML和代码时,我也发现了一些值得关注的差异:

  1. UML中的getBookShelf()方法在代码中确实存在,但它在UML类图中标注为private而代码中实际是private——这是一致的。
  2. borrowBookC类型演进:从HW13的HashSet<LibraryBookId>到HW15的HashMap<LibraryBookId, BorrowLimit>,这是一个类型层面的"破坏性变更",必须同时在类图和代码中反映。
  3. v3新增的orderNewBook()方法是一个空的占位方法,代码中有定义但业务逻辑未实现——这是为了满足某种接口约束的预留,在UML中同样需要标注。

2.4 架构演进的阶段性特征

维度HW13HW14HW15
类数量71011
位置状态数466
操作类型5810
UML图类型类图类图+状态图类图+状态图+顺序图
核心抽象图书流通+评分/精品+信用/期限
Student复杂度简单hashset持有不变引入credit+BorrowLimit

可以看到,架构的增长符合"开闭原则"——每次迭代主要做加法而非修改已有的稳定模块。BookShelf、BorrowReturnOffice、AppointmentOffice、Book这些类在三次迭代中几乎没变,而Library和Student逐步承接了新的复杂度。这种稳定-变化分离的设计使得每次迭代的改动范围可控。


三、大模型辅助正向建模

3.1 本单元中的大模型使用场景

在本单元的正向建模过程中,我尝试了使用大模型辅助完成以下几个环节:

  1. 需求分析到类图草稿的转换:将中文需求文档(如"题目要求.md")输入大模型,让它帮梳理出初始类结构。

  2. 从UML描述生成代码框架:在StarUML中完成类图后,让大模型根据类图描述生成带方法签名的Java骨架代码。

  3. 代码与UML的一致性检查:让大模型读取.java文件和.mdj文件两端内容,对比差异并指出不一致之处。

  4. 状态图的生成与验证:使用staruml-skills工具链(staruml_state_tool)配合大模型,从代码中的@Trigger注解反向生成状态图,验证状态图与实现的吻合度。

3.2 大模型在正向建模各阶段的效能评估

阶段大模型表现典型问题
需求抽象较好能准确识别"书架""借还处""预约处"等实体,但易遗漏隐式约束(如"用户只能借一本B类书")
类图设计一般能产出基本结构,但对性能敏感的细节(如BookShelf用HashSet<Integer>管理副本号以支持O(1)移除最小副本)缺乏考量
代码生成较好给定清晰的类图和接口描述,能快速产出可编译的代码骨架
一致性检查一般能发现明显的缺失/多余方法,但对类型变化(如HashSet→HashMap的演进)的语义分析不够精准
重构建议较弱倾向于"教科书式"重构建议(如"提取接口""使用策略模式"),缺乏针对具体业务约束的工程判断

3.3 引导大模型完成复杂架构设计的有效策略

经过本单元的实践,我总结出以下几条引导大模型完成架构设计任务的有效方法:

策略一:分阶段逐步细化,而非一步到位

不要期望一次性给出完整需求就能得到完美设计。更有效的方式是"多轮对话-逐层细化":

第一轮:请根据以下需求,识别系统中的核心实体(领域类)
第二轮:请分析这些实体之间的关联关系和多重性
第三轮:请为每个实体设计方法和属性
第四轮:请检查设计是否满足以下约束条件...

每轮聚焦一个问题维度(实体识别→关系→方法→约束),大模型的输出质量和一致性显著高于一次性生成。

策略二:用结构化格式约束输出

大模型"自由发挥"时容易产生边界模糊的设计。使用明确的格式约束可以有效强制模型的条理性:

请按以下表格格式输出类设计:
| 类名 | 职责(一句话) | 关键属性 | 关键方法 | 关联类 |

这种结构化输出使得设计结果便于审查和迭代,也更容易从中提取代码骨架。

策略三:将业务规则显式罗列

大模型对自然语言中的"隐含规则"捕捉能力有限。对于图书馆系统这类规则密集的场景,将业务规则用if-then形式化语言明确列出,能显著提升设计质量:

规则1:IF 图书为A类 THEN 不可借阅 AND 不可预约
规则2:IF 用户已持有一本B类书 THEN 不可再借B类书
规则3:IF 用户已持有某ISBN号的C类书 THEN 不可再借该ISBN的C类书

在给出这段规则列表后再要求生成类设计,大模型会更自然地将规则分配到对应的类方法中(如规则2和3归入Student.canBorrow())。

策略四:利用代码-模型的双向验证

在完成编码后,让大模型逆向检查:

请对比以下UML类图描述和Java源代码,列出所有不一致之处:
1. UML有但代码无的属性/方法
2. 代码有但UML无的属性/方法
3. 类型或签名不匹配的方法

这种双向验证比单向生成更有价值,也能帮助发现手工编码中偏离设计的部分。

3.4 大模型辅助建模的局限性

尽管大模型在以上场景中表现可用,但本单元的实践也让我清醒认识到其局限:

  1. 缺乏领域状态机直觉:对于"整理流程中每本书只能移动一次,但移往预约处不计入限制"这类复杂的流程约束,大模型需要多轮澄清才能正确建模

  2. 增量修改容易迷失:在三轮迭代中,大模型更擅长"从零生成"而非"在已有代码基础上精确定位修改点"。它倾向于重写大段代码而非最小化修改。

  3. 物理世界建模的前提不清:大模型不知道"为什么B类书借阅期限是15天、C类是30天"——这些规则背后的"为什么"是人类设计者在需求分析阶段要完成的,大模型只能执行而不能质疑。

四、架构设计思维

第一单元

抽象思维:第一单元实现了多项式化简得的功能。其中我在解析初始字符串时,将其抽象为了exp,termfactor三个层次的概念。同时为每一个层次设计了toPoly()方法来完成化简的功能。这种分层抽象的思维方式使得代码结构清晰,职责分明。而面对表达式因子我们又在其toPoly()方法中使用了递归调用的方式来处理括号内的表达式,这也是一种抽象思维的体现。

第二单元

生产者-消费者模式:第二单元是一个多线程电梯。我使用了经典的生产者-消费者模式来设计电梯的调度系统。输入解析线程作为生产者,不断从输入中解析出电梯请求并放入请求队列;电梯调度线程作为消费者,从请求队列中取出请求并执行相应的电梯操作。这种模式使得输入解析和电梯调度解耦,提高了系统的灵活性和可维护性。

第三单元

时间复杂度:这个单元的结构其实是按照给定的JML规范来实现的。但是其中的具体数据结构等是要求自己设计的,同时这个单元相比于前面的单元对于时间复杂度要求更高,让我学会了在设计数据结构以及方法具体实现时,都要考虑时间复杂度的问题。

第四单元

第四单元又回到了自己独立设计架构的模式,在这个单元中我学会了如何进行正向建模以及迭代开发。通过三次作业的不断迭代,我逐步完善了小型图书馆管理系统的架构设计,并且在每次迭代中都保持了UML类图与代码实现的一致性。这种正向建模的方法使得我的设计更加清晰和合理,同时也提高了代码的可维护性和扩展性。

五、测试思维的演进

  1. 自己捏数据测试:在第一单元中,我主要是通过自己构造一些多项式表达式来测试化简功能的正确性。这种方法虽然可以覆盖一些基本的情况,但很难全面覆盖所有可能的输入情况。
  2. 随机数据测试:在第二单元中,我开始使用随机数据来测试电梯系统的稳定性和性能。通过生成大量随机的电梯请求,我能够更好地模拟实际使用中的情况,并且发现一些边界条件下的问题。
  3. 编写测试方法:在第三单元中,我开始编写一些测试方法来验证我的实现是否满足JML规范。这些测试方法不仅覆盖了正常情况,还包括了一些异常情况,确保了代码的健壮性。

六、总结

  1. 通过各个单元作业的完成,我不仅对于之前Pre课程学习的面向对象的基本内容有了更为深入的理解,同时也通过多线程以及modeling language的学习,扩展了自己的知识体系。
  2. 同时在四个单元的测试中,我也更加深入的理解了测试的重要性以及如何设计测试用例更为高效且全面。
...全文
29 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

309

社区成员

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

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