☰
UML建模实战:图书馆管理系统从用例图到类图的完整设计指南
2026/10/3 7:51:02 网站建设 项目流程

简介:这份PDF面向软件工程课程设计、UML建模学习与考试复习人群,围绕图书馆管理系统给出完整的建模方案,帮助读者理解需求分析到动态建模的完整流程。资源包共1个PDF文件,约1.55MB,内容以图文结合方式呈现,便于对照阅读与打印复习。系统覆盖读者管理、书籍管理、借阅管理与系统管理四大功能模块,并展开用例图、时序图、活动图、状态图与类图五类模型:用例图区分管理员与读者的操作边界,时序图细化借书、还书与罚款的对象交互顺序,活动图梳理借书、还书与预订的流程分支,状态图刻画书籍从新加到在库、借出、预订的状态迁移,类图则给出reader、admin、Title、Item等核心类的属性与操作。已有414人学习,适合需要快速掌握UML建模方法、完成课程作业或备考的读者参考。

1. 从一份 UML 图纸到能跑的系统:图书馆管理系统的建模底稿

很多同学做课程设计时,拿到需求第一反应是打开 IDE 写代码,结果写到一半发现借书和还书的边界条件对不上,回头改表结构,改完又发现权限模型没设计,最后交上去的类图跟代码完全两张皮。这份《图书馆管理系统用例图、活动图、类图、时序图.pdf》解决的正是这个顺序问题——它把需求分析、动态建模、静态建模三块串成一条线,先让你把业务规则想清楚,再落到图上,最后才谈实现。文档覆盖了读者管理、书籍管理、借阅管理、系统管理四个功能域,给出了借书、还书、罚款三条核心时序,以及书籍从新加到在库、借出、预订、销毁的完整状态流转。适合正在做 UML 课程设计、软件工程实验、或者准备软考中级 UML 建模部分的从业者,也适合想补一补面向对象分析基本功的后端同学。它不教你怎么写 Java,但能让你在动手之前把该问的问题都问一遍。

2. 需求分析怎么落到用例图:四个功能域与两类角色的边界

2.1 从功能需求到用例的映射逻辑

需求分析里列了四个功能域:读者管理、书籍管理、借阅管理、系统管理。很多人画用例图时直接把这四个功能域当成四个用例,这是最常见的翻车点。用例的粒度应该对齐"一次完整的业务目标",而不是对齐模块划分。比如"书籍管理"不是一个用例,它至少拆成书籍注册、书籍修改、书籍注销三个;"借阅管理"里的借书、还书、预订、续借、过期处理、丢失处理,每一个都是独立用例,因为它们的触发条件、前置条件、后置条件都不一样。

文档里把角色分成管理员和读者两类,这个划分是对的,但要注意一个细节:自动借还书机在需求里被提到,它既不是管理员也不是读者,而是一个外部系统参与者。画用例图时应该把它作为 Actor 放在右侧,跟管理员和读者并列,而不是塞进某个用例内部。常见做法是:管理员关联登录、书籍增删改、读者增删改、借阅管理、自动借书机管理;读者关联登录、借书、还书、查询、预订、逾期处理、丢失处理、自动借书机使用。注意"逾期处理"这个用例,管理员和读者都关联,但含义不同——管理员是执行罚款操作,读者是缴纳罚金,如果画成一个用例,后面时序图会打架。

2.2 用例描述表的填写规范

光有图不够,每个用例至少要配一张描述表,否则后面画时序图时你会发现不知道谁先动。下面这张表是我按文档内容整理的借书用例描述,可以直接抄进你的实验报告:

字段内容
用例名称借书
参与者读者、管理员
前置条件读者已登录,借书证有效,未达最大借书数量,无过期未还书籍
主事件流1. 读者将书交给管理员;2. 管理员扫描借书证;3. 系统验证读者资格;4. 管理员扫描书籍条码;5. 系统检查书籍是否可借;6. 系统创建外借记录;7. 更新书籍状态为借出
备选流3a. 读者资格不符,提示原因并结束;5a. 书籍已被预订,取消预订后继续;5b. 书籍为不可借状态,拒绝借书
后置条件书籍状态变为借出,读者借阅记录新增一条,借书时间被记录

这张表里的备选流是重点,很多人只写主流程,结果活动图里没有分支,答辩时老师一问"如果读者借书超量了怎么办"就答不上来。把备选流写全,活动图的分支节点自然就有了。

2.3 用例之间的关系:include 和 extend 别用反

文档里没有明确写用例之间的关系,但这是 UML 用例图的必考项。借书和还书都包含"登录系统"这个步骤,所以登录应该被 include 进借书和还书,而不是让读者直接关联登录。预订和借书之间是 extend 关系——预订是在借书流程基础上增加的一个可选行为,不是每次借书都必须预订。判断标准很简单:如果 A 用例执行时 B 用例一定执行,用 include;如果 B 用例只在特定条件下才插入 A 用例,用 extend。我见过把罚款做成还书的 include,结果还书时序图里强制出现罚款步骤,这就是关系用反了导致的连锁错误。

3. 动态建模三件套:时序图、活动图、状态图的联动画法

3.1 借书时序图的对象与消息顺序

时序图的核心是对象生命线和消息箭头。文档里借书时序图涉及的对象有:管理员、读者、借书界面、读者管理模块、书籍管理模块、借阅管理模块、数据库。消息顺序按文档描述整理如下:

读者 -> 管理员: 提交借书证和书籍 管理员 -> 借书界面: login() 借书界面 -> 读者管理模块: checkstu_card() 读者管理模块 -> 数据库: 查询读者信息 数据库 --> 读者管理模块: 返回读者记录 读者管理模块 --> 借书界面: 返回验证结果 借书界面 -> 读者管理模块: showinformation() 借书界面 -> 借阅管理模块: borrow() 借阅管理模块 -> 读者管理模块: getreaders() 读者管理模块 --> 借阅管理模块: 返回可借信息 借阅管理模块 -> 书籍管理模块: gettitle() 书籍管理模块 -> 数据库: 查询书目信息 数据库 --> 书籍管理模块: 返回书目记录 书籍管理模块 --> 借阅管理模块: 返回书目信息 借阅管理模块 -> 书籍管理模块: getreservation() 书籍管理模块 --> 借阅管理模块: 返回预订状态 借阅管理模块 -> 借阅管理模块: getnoreservation() 借阅管理模块 -> 数据库: create(borrower, item) 数据库 --> 借阅管理模块: 返回创建结果 借阅管理模块 --> 借书界面: 借书成功 借书界面 --> 管理员: 显示成功信息

这里有个容易忽略的点:getreservation() 和 getnoreservation() 是两个不同的消息,前者查书籍是否被预订,后者处理"没被预订或取消预订"的情况。画图时不要合并成一个,否则预订状态的分支逻辑就丢了。另外 create(borrower, item) 的参数是两个对象,不是两个字符串,这体现了面向对象的设计思路——借阅记录关联的是读者对象和书籍对象,而不是读者 ID 和书籍 ID。

3.2 还书与罚款时序图的差异点

还书时序图比借书短,但多了一个关键判断:书籍是否过期。文档里的还书流程是:管理员扫描书籍 → getitem() 取得书籍条目 → 判断是否过期 → 未过期则 update() 更新书目和读者信息 → 还书成功。如果过期,则进入罚款时序图:系统自动计算过期天数和罚款金额 → 读者缴纳罚金 → update() 更新借阅信息。

这两个时序图的对象基本一致,但消息不同。还书用的是 getitem() 和 update(),罚款用的是计算函数和缴费确认。画的时候注意:罚款时序图里不应该出现借书相关的消息,还书时序图里也不应该出现罚款计算,除非你用 alt 组合片段把两个分支都画出来。我一般建议分开画,因为分开后每个图的消息数量控制在 10 条以内,可读性最好。

3.3 活动图的分支与合并节点

活动图描述的是流程,文档里给了借书、还书、预订三个活动图。借书活动图的分支最多:扫描借书证后要判断借书数量是否达上限、是否有过期书籍;扫描书籍条码后要判断是否不可借、是否被预订。这些判断在活动图里用菱形表示,每个菱形有两条出边,一条是条件成立,一条是不成立。

开始 -> 扫描借书证 -> [借书数量未达上限且无过期] -> 扫描书籍条码 -> [借书数量已达上限或有过期] -> 提示原因 -> 结束 扫描书籍条码 -> [书籍可借且未被预订] -> 更新书籍信息和借阅信息 -> 记录借书时间 -> 结束 -> [书籍不可借] -> 拒绝借书 -> 结束 -> [书籍已被预订] -> 取消预订 -> 更新书籍信息和借阅信息 -> 结束

注意"取消预订"这个动作在活动图里是一个独立的活动节点,不是判断节点。很多人把"是否被预订"和"取消预订"画成一个菱形,这是错的——判断只负责分流,取消预订是分流后要执行的操作。还书活动图的分支在"是否过期"上,过期则要求缴罚款,缴完再更新信息;未过期直接更新。预订活动图的分支在"是否可预订"和"是否已被预订或外借"上,两个条件都通过才允许登录并预订。

3.4 状态图:书籍的五个状态与迁移条件

文档里书籍状态图给出了新加、在库、借出、预订、可用五个状态。迁移关系是:新加书籍 → 在库;在库 → 借出(外借);在库 → 预订(预订);预订 → 借出(预订后外借);预订 → 可用(超出预订时间或取消预订);借出 → 可用(归还);在库 → 销毁(淘汰、损坏、丢失)。这里有个细节:文档说"处于预订状态时也可以外借",所以预订 → 借出这条边是存在的,不要漏掉。

状态图的价值在于帮你发现隐藏的业务规则。比如"在库 → 销毁"这条边,需求里说的是"旧书销毁功能,对于淘汰、损坏、丢失的书目可及时对数据库进行修改",但损坏和丢失其实是两种不同情况——损坏可能是读者损坏后赔偿,丢失可能是读者丢失后赔偿,这两种情况下书籍状态应该先变成"丢失"或"损坏",再进入销毁,而不是直接从在库跳到销毁。如果你在状态图里补上这两个中间状态,后面做数据库设计时就会多出两个状态字段,业务逻辑会更严谨。

4. 类图与数据库设计的对应关系:从七个类到表结构

4.1 七个核心类的属性与方法

文档里的类图包含七个类:Reader、Admin、Title、Item、Borrow、Reservation、PersistentStore。逐个拆解:

Reader 类属性有 reader_id、reader_Name、Address、class、borrowed,操作有 addborrowed()、deleteborrowed()、reservation()。注意 borrowed 这个属性,它应该是一个集合类型,存放该读者当前借阅的所有 Item 引用,而不是一个字符串。

Admin 类属性有编号和姓名,操作是书籍增删改和读者增删改。这里有个设计问题:Admin 的操作直接操作 Title 和 Reader,还是通过 PersistentStore 中转?文档说"其他对与书籍有关的活动都要经过其存储类",所以 Admin 应该依赖 PersistentStore,而不是直接依赖 Title。

Title 类属性有 name、author、book_id,代表书目信息。Item 类属性有 id,操作有 reserve()、find_on_title(),代表具体某本书。Title 和 Item 是一对多关系——一个书目可以有多个副本,每个副本是一个 Item。

Borrow 类属性有 ISBN、date,代表借阅记录。Reservation 类属性有 date、ISBN、UserID,代表预订记录。PersistentStore 类是持久化存储,所有对数据库的读写都经过它。

4.2 类之间关系的代码映射

类图画完之后,落到代码里就是类和接口的定义。下面用 Python 伪代码示意核心关系:

class Title: def __init__(self, book_id, name, author): self.book_id = book_id self.name = name self.author = author self.items = [] # 一个书目对应多个 Item class Item: def __init__(self, item_id, title): self.item_id = item_id self.title = title # 关联到 Title self.status = "available" # available/borrowed/reserved/destroyed def reserve(self, reader): if self.status == "available": self.status = "reserved" return Reservation(reader.reader_id, self.title.book_id) def find_on_title(self, title): return [item for item in title.items if item.status == "available"] class Reader: def __init__(self, reader_id, name): self.reader_id = reader_id self.name = name self.borrowed = [] # 当前借阅的 Item 列表 def addborrowed(self, item): self.borrowed.append(item) def deleteborrowed(self, item): self.borrowed.remove(item) class Borrow: def __init__(self, reader, item, date): self.reader = reader self.item = item self.date = date class Reservation: def __init__(self, user_id, isbn, date=None): self.user_id = user_id self.isbn = isbn self.date = date

这段代码里最关键的是 Item 的 status 字段,它对应状态图里的五个状态。Title 和 Item 的一对多关系用列表实现,Borrow 和 Reservation 作为关联类记录多对多关系。PersistentStore 在代码里通常对应一个 Repository 层,负责把对象序列化到数据库。

4.3 类图到表结构的转换规则

类图转数据库表,一般按以下规则:普通类转一张表,属性转字段,主键选一个唯一标识(Reader 用 reader_id,Title 用 book_id,Item 用 item_id)。一对多关系在多的一方加外键,比如 Item 表加 title_id 外键。多对多关系拆成中间表,Borrow 表就是 Reader 和 Item 的中间表,字段包括 reader_id、item_id、borrow_date。Reservation 表是 Reader 和 Title 的中间表,字段包括 reader_id、book_id、reserve_date。

类对应表主键外键
Readerreaderreader_id无
Adminadminadmin_id无
Titletitlebook_id无
Itemitemitem_idtitle_id
Borrowborrowborrow_idreader_id, item_id
Reservationreservationreserve_idreader_id, book_id

注意 Item 表里要加 status 字段,对应状态图的五个状态值。Borrow 表里要加 return_date 和 fine 字段,分别记录归还日期和罚款金额,这两个字段在类图里没画出来,但业务上必须有,属于"类图遗漏但实现时必须补"的典型情况。

5. 避坑与排查:建模过程中最容易翻车的五个地方

5.1 用例粒度太粗导致时序图画不下去

现象:用例图里只有一个"借阅管理"用例,画时序图时发现借书、还书、罚款的消息全混在一起,对象不知道找谁。 原因:用例粒度对齐了模块而不是业务目标,一个用例承担了多个独立流程。 解决:把借阅管理拆成借书、还书、预订、续借、过期处理、丢失处理六个用例,每个用例单独画时序图。判断标准是:如果一个用例的主事件流超过 10 步,或者备选流超过 3 条,就该考虑拆分。

5.2 时序图里把数据库当对象画

现象:时序图里出现"数据库"生命线,所有消息都发给数据库,业务对象反而没有消息。 原因:把时序图当成了 SQL 执行流程图,忽略了业务对象之间的交互。 解决:时序图的对象应该是业务对象(读者管理模块、书籍管理模块、借阅管理模块),数据库只在持久化操作时出现,而且通常用 PersistentStore 代替。业务逻辑的消息应该在业务对象之间传递,比如借阅管理模块调用读者管理模块的 getreaders(),而不是直接查数据库。

5.3 活动图缺少合并节点导致流程无法结束

现象:活动图里每个分支都有出边,但分支结束后没有合并节点,流程悬在半空。 原因:只画了判断节点,忘了画合并节点,或者把合并节点和判断节点画成了同一个菱形。 解决:每个分支结束后,如果后续流程相同,必须用一个合并节点把分支收回来。借书活动图里"取消预订"和"未被预订"两条分支最终都要走到"更新书籍信息和借阅信息",所以需要一个合并节点。判断节点和合并节点的区别是:判断节点一进多出,合并节点多进一出。

5.4 类图里把关联关系画成依赖关系

现象:Reader 类和 Borrow 类之间画的是虚线箭头,代码里却用成员变量持有对方引用。 原因:混淆了关联和依赖。关联是长期持有的关系(成员变量),依赖是临时使用的关系(方法参数或局部变量)。 解决:Reader 和 Borrow 是关联关系,Reader 对象长期持有 Borrow 列表,用实线箭头。Borrow 和 Item 也是关联关系。只有像 Admin 调用 PersistentStore 的 save() 方法这种临时调用,才用依赖关系。判断标准:如果 A 对象在生命周期内一直持有 B 对象的引用,就是关联;如果 A 只在某个方法里临时创建或传入 B,就是依赖。

5.5 状态图遗漏异常状态导致数据库字段不够用

现象:状态图里只有在库、借出、预订三个状态,但需求里提到了书籍丢失和损坏,实现时发现 status 字段不知道填什么。 原因:状态图只画了正常流程,没有覆盖异常流程。 解决:在状态图里补上"丢失"和"损坏"两个状态,以及从"借出"到"丢失"、从"借出"到"损坏"的迁移边。这样数据库的 status 字段至少需要五个枚举值:available、borrowed、reserved、lost、damaged。如果需求里还有"淘汰"状态,再加一个 destroyed。状态图越完整,后面写代码时 if-else 越少。

6. 从图纸到代码的最后一公里:用 PlantUML 复现四张核心图

文档里的图是静态图片,改起来麻烦。我一般会用 PlantUML 把用例图、时序图、活动图、类图重新画一遍,好处是改文字就能改图,而且能直接嵌进 Markdown 或 Confluence。下面给出借书时序图和类图的两个模板,复制到 PlantUML 编辑器里就能渲染。

@startuml actor 管理员 participant "借书界面" as UI participant "读者管理模块" as RM participant "借阅管理模块" as BM participant "书籍管理模块" as TM database "PersistentStore" as DB 管理员 -> UI: 提交借书证和书籍 UI -> RM: checkstu_card() RM -> DB: 查询读者信息 DB --> RM: 返回读者记录 RM --> UI: 返回验证结果 UI -> RM: showinformation() UI -> BM: borrow() BM -> RM: getreaders() RM --> BM: 返回可借信息 BM -> TM: gettitle() TM -> DB: 查询书目信息 DB --> TM: 返回书目记录 TM --> BM: 返回书目信息 BM -> TM: getreservation() TM --> BM: 返回预订状态 BM -> BM: getnoreservation() BM -> DB: create(borrower, item) DB --> BM: 返回创建结果 BM --> UI: 借书成功 UI --> 管理员: 显示成功信息 @enduml

这段 PlantUML 的关键是 participant 和 database 的区分,database 只用于持久化操作。消息箭头用->表示同步调用,-->表示返回。注意 getnoreservation() 是自调用消息,用BM -> BM表示。

@startuml class Reader { -reader_id: String -reader_Name: String -Address: String -class: String -borrowed: List<Item> +addborrowed(item: Item) +deleteborrowed(item: Item) +reservation(title: Title) } class Title { -book_id: String -name: String -author: String +getItems(): List<Item> } class Item { -item_id: String -status: String +reserve(reader: Reader) +find_on_title(title: Title): List<Item> } class Borrow { -borrow_id: String -date: Date -return_date: Date -fine: Double } class Reservation { -reserve_id: String -date: Date -user_id: String -isbn: String } Reader "1" --> "*" Borrow Borrow "*" --> "1" Item Reader "1" --> "*" Reservation Reservation "*" --> "1" Title Title "1" --> "*" Item @enduml

类图里我把 Borrow 和 Reservation 的字段补全了,borrow_id、return_date、fine 这三个字段在文档的类图里没画,但实现时必须有。关系的多重性用"1" --> "*"表示,Reader 和 Borrow 是一对多,Borrow 和 Item 是多对一,Title 和 Item 是一对多。

验证方法很简单:把 PlantUML 生成的图和文档里的图对照,如果消息顺序、类属性、关系多重性都一致,说明你理解到位了。如果发现文档里有的消息你的图里没有,或者你的图里多了文档没提的消息,回去查需求分析,看是不是漏了某个备选流。我每次做完建模都会把四张图并排放在一起,检查用例图里的每个用例是否有时序图对应,时序图里的每个对象是否在类图里出现,活动图的每个分支是否在状态图里有对应状态。这套交叉验证走一遍,课程设计答辩基本不会被问倒。从那以后我每次做 UML 建模都强制走一遍"用例→时序→活动→状态→类图"的闭环检查,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询