简介:航空订票系统是典型的信息管理系统设计案例,适合软件工程、系统分析与设计课程的学生,以及正在学习UML建模的开发者参考。资源以UML可视化建模为主线,覆盖用例图、类图、包图、顺序图、协作图、状态图、活动图、组件图与部署图,完整展示了新用户注册、登录验证、航班查询、机票预订与退票等核心业务的建模过程。作者结合Rose工具绘制图表,并针对用户权限、信用评价交互、类间继承与依赖关系等关键点做了梳理,能帮助读者理解如何将抽象业务需求转化为规范的系统模型。资源共1个文件,为PDF格式,整体大小约2.04MB,便于直接阅读与存档。目前已有1958人浏览学习,适合作为课程设计参考或UML实战入门资料。
1. 航空订票系统的UML建模设计:为什么画图比写代码更先决
一份航空订票系统的UML建模设计文档摆到面前,新手的第一反应通常是翻类图画得是否工整,我的第一反应是先看用例图和时序图。原因很简单:用例图定义系统边界,时序图定义对象协作,这两张图如果一开始就歪掉,后面类图、状态图画得再规范,落到代码时也会处处打架。
这套笔记把建模过程拆成一条可照做的路线:先厘清角色与用例,再做静态结构建模,再做动态行为建模,最后给出避坑清单并反推数据库表与接口。适合正在做课程设计、准备软考中级,或者第一次独立承担需求建模的开发者。
2. 用例建模:把订票业务拆成角色、用例与边界
用例是整个建模的地基。很多文档把用例图画成一张到处是箭头的功能清单,评审会上被问到“支付网关算哪边的角色”“取消订单和退款是几个用例”就答不上来。这里的做法是先定角色,再拆用例,最后补用例描述,每一步都对应可以交给别人评审的产出物。
2.1 角色识别:乘客、管理员与航空公司,不要漏掉外部系统
动手画图之前,先从业务描述里找主语。航空订票系统最典型的主语是普通乘客、系统管理员、航空公司票务和支付网关。其中支付网关是最容易被漏掉的一个——它不在你系统内部,但它参与了支付过程,必须作为外部参与者出现在用例图里,否则后续时序图里支付回调这段就找不到归属。
| 角色 | 定位 | 与系统的关系 |
|---|---|---|
| Passenger(乘客) | 核心用户,注册后可购票 | 查询航班、预订订单、支付、取消、值机 |
| Visitor(匿名访客) | 未登录用户 | 只能查询航班,订票前要引导登录 |
| Admin(系统管理员) | 运营方 | 维护航班、管理价格、处理售后订单 |
| AirlineAgent(航司票务) | 外部柜员 | 处理线下值机和航班变动通知 |
| PaymentGateway(支付网关) | 外部系统 | 接收支付请求,回调支付结果 |
判断一个角色到底放在系统边界内还是边界外,就看一条:这个角色是否由你所在的系统负责实现。支付网关不是你开发的,它只是被调用,那就画在边界外;乘客登录状态如果由第三方认证服务提供,那认证服务也要画在边界外,而不是做成一个用例下的按钮。
2.2 用例图怎么画:从订票主流程拆出核心用例
用例不是操作步骤,而是参与者能完成的有业务价值的目标。订票系统里“点击查询按钮”不是用例,“查询航班”才是;“输入乘机人信息”不是用例,“预订机票”才是。拆法可以按四步走:
- 列出业务动词:查询、预订、支付、取消、值机、退改签、航班管理、订单管理。
- 过滤掉系统内部动作:把“校验登录”“生成订单号”“发送短信”这类实现细节先放到用例描述里,不单独画成用例。
- 确认每个用例都有明确的参与者,且参与者能观察到结果。
- 画一个系统边界矩形,用例放里面,角色放外面。
| 用例名 | 参与者 | 前置条件 | 后置条件 |
|---|---|---|---|
| 查询航班 | 乘客 / 匿名访客 | 无 | 返回可售航班列表 |
| 预订机票 | 乘客 | 已登录,存在可售航班 | 生成待支付订单,锁定舱位 |
| 支付订单 | 乘客 | 有待支付订单 | 订单状态变为已支付 |
| 取消订单 | 乘客 | 订单已支付且未出票 | 释放舱位,生成退款单 |
| 办理值机 | 乘客 | 订单已出票 | 生成电子登机牌 |
| 管理航班 | 管理员 | 已登录后台 | 航班时刻表更新 |
| 处理退改签 | 管理员 | 乘客提交申请 | 原订单状态变更,新订单生成 |
这里要注意“取消订单”和“退票”是不是一个用例。我的习惯是按触发结果拆:用户主动在前台取消未出票订单,和用户提交退票申请由后台审核,两者动作和规则不同,拆成两个用例,后续状态图也更好画。
2.3 用例描述的写法:主事件流、备选流与前置条件的落地模板
用例图只画椭圆和连线是交不了差的,真正有价值的是每个用例的文字描述。软考中级案例题里,经常直接给一段业务描述让你补全用例描述,答题套路就是下面这个模板:
| 字段 | 内容 |
|---|---|
| 用例名称 | 预订机票 |
| 参与者 | 乘客 |
| 前置条件 | 乘客已登录;存在可售的航班舱位 |
| 主事件流 | 1. 乘客提交出发地、目的地和日期;2. 系统返回可售航班列表;3. 乘客选择航班与舱位等级;4. 系统校验登录状态;5. 系统创建待支付订单;6. 系统返回订单号与支付金额 |
| 备选流 | 3a:所选舱位已被占用,系统提示重新选择;4a:登录已过期,系统引导重新登录后回到步骤3;5a:库存扣减失败,系统回滚并提示稍后重试 |
| 后置条件 | 生成一条待支付订单,对应舱位被锁定 |
主事件流里的每一步,后面画时序图时几乎能一对一地变成消息。这也是为什么我在评审时优先看用例描述而不是看图的原因——描述写得含糊,图一定画不扎实。备选流是很多人偷懒的地方,但支付超时、库存不足、登录失效这些分支,恰恰是系统最容易被用户骂醒的场景。
3. 静态结构建模:类图与对象图的绘制规范和箭头含义
用例解决了“哪些人能做什么”,接下来要回答“系统里有哪些实体、实体之间怎么互相持有”。这一章的核心是类图。类图也是软考中级里最常考的一类图,尤其是画箭头和判断关系,基本是必考点。
3.1 实体类识别:从用例名词里抽类,而不是从数据库反推
有人习惯先把数据库表设计好,再照着表反向画类图,这个习惯我不推荐。建模设计文档的价值在早期,先定义好对象再谈存储,顺序反了会漏掉业务对象。正确做法是从用例描述里做名词过滤,把候选实体捞出来。
| 候选类 | 关键属性 | 来源用例 |
|---|---|---|
| User | userId, userName, mobile, passwordHash | 注册、登录、预订机票 |
| Flight | flightNo, departure, arrival, takeoffTime, landingTime, cabinCapacity | 查询航班、管理航班 |
| Order | orderId, userId, status, totalAmount, createdAt | 预订机票、支付订单、取消订单 |
| OrderItem | orderItemId, orderId, flightId, cabinClass, passengerName | 预订机票、办理值机 |
| Payment | paymentId, orderId, amount, channel, callbackTime | 支付订单 |
类图命名有个约定:类名用名词单数,属性用小驼峰,类型放在冒号后。类名和属性名一旦确定,后面生成数据库表、写后端实体类都用同一套词,减少翻译损耗。另外,DTO 和 Entity 从一开始就分开建模,不然订单类和订单视图对象混在一起,类图会变成一锅粥。
3.2 UML类图箭头含义:依赖、关联、聚合、组合、泛化的区分
类图翻车的重灾区就在箭头上。UML 里关系一共五类,每一类的画法和语义都不一样,评审时被考官盯着箭头问是常有的事。
| 关系 | 画法 | 语义 | 订票系统里的例子 |
|---|---|---|---|
| 泛化 | 实线,空心三角箭头指向父类 | is-a,继承 | Passenger 继承 User |
| 实现 | 虚线,空心三角箭头指向接口 | implements,实现接口 | PaymentService 实现 PaymentGatewayClient |
| 依赖 | 虚线,普通箭头指向被使用者 | 方法参数、返回值、局部变量里用到对方 | OrderService 依赖 PaymentClient |
| 关联 | 实线,普通箭头 | 长期持有的引用,has-a | User 关联 Order,一个用户有多张订单 |
| 聚合 | 实线,空心菱形在整体侧 | 弱拥有,整体销毁个体仍存在 | Order 聚合 Payment,订单删除后支付流水保留 |
| 组合 | 实线,实心菱形在整体侧 | 强拥有,整体销毁个体一起销毁 | Order 组合 OrderItem,订单删除明细一起删 |
最容易混的是聚合和组合,判断标准就一条:整体没了,部分还在不在。支付流水要在订单删除后继续保留用于对账,所以是聚合;订单明细跟着订单走,订单没了明细没有存在意义,所以是组合。菱形永远画在整体那一侧,箭头指向的是被拥有的那一侧,方向画反是高频扣分点。
3.3 类图里的属性与方法:MVC分层状态如何影响建模
类图不是把所有实体堆在一张图里就完事。后端按 Controller、Service、Repository 分层时,各层有自己的类视角,全塞进一张图会导致依赖关系乱成一团。我一般把类图拆成两张:一张实体关系图,只画 Entity 和它们之间的关系;一张核心协作图,只画 Service 之间的依赖。
类图里的方法不需要把所有 getter/setter 都画出来,只画有业务含义的行为。比如 Order 类里要有一个 pay(),OrderItem 不需要有任何业务方法,它只是数据的载体。对于 Service 类,方法签名要写完整,因为后面时序图的消息名会和它对上。分层建模时遵循三条规则:Controller 只依赖 Service 接口;Service 依赖 Repository 和领域实体;Entity 不反向依赖 Service。
提示:这是评审时最容易被问“为什么类图里没有 orderStatus 属性”的地方。状态字段务必在实体类里画出来,否则后面状态图就是无源之水。
4. 动态行为建模:时序图、状态图与活动图的分工
类图描述的是“静态结构”,但系统真正跑起来是对象之间不断发消息、状态不断变化的过程。动态行为建模就是把这部分讲清楚。UML 里动态结构图包括时序图、通信图、状态图和活动图,这一章把订票系统最常用的三张讲透。
4.1 时序图:订票主流程的寿命线与消息顺序
时序图画的是“谁在什么时候调用了谁的哪个方法”。订票主流程的消息顺序如下:
- 乘客向系统发起查询航班消息,参数是出发地、目的地和日期。
- 系统向数据库或航班服务查询可售航班列表。
- 乘客选择航班和舱位,系统返回可售舱位与价格。
- 乘客提交乘机人信息,系统创建待支付订单。
- 系统向支付网关发起创建支付请求。
- 支付网关回调支付结果,系统更新订单状态为已支付。
- 系统异步触发出票,成功后通知乘客出票信息。
消息命名统一用动宾结构,比如 createOrder(orderInfo)、queryFlight(queryParam)、paymentCallback(paymentResult)。这一步直接决定了后续接口文档能不能写清楚。时序图不需要每个请求都画一条返回虚线,只画关键的返回消息就好,否则图上一大堆回箭头,时间线根本没法读。返回消息注意要和请求消息使用相同命名,只是方向相反。
4.2 状态图:订单状态流转是建模设计的重点
订单是订票系统里生命周期最长的对象,从创建到完成要经过多个稳定的中间状态,这正好是状态图的用武之地。状态图建模不画动作步骤,画的是“在某个状态下,发生了某个事件,跳到另一个状态”。
| 当前状态 | 触发事件 | 下一状态 |
|---|---|---|
| (初始) | 乘客提交订单 | 待支付 |
| 待支付 | 支付成功回调 | 已支付 |
| 待支付 | 用户取消 / 支付超时自动关闭 | 已取消 |
| 已支付 | 出票成功 | 已出票 |
| 已出票 | 乘客办理值机 | 已值机 |
| 已值机 | 航班起飞并落地 | 已完成 |
| 已支付 | 乘客提交退票申请 | 退票中 |
| 退票中 | 审核通过并退款 | 已退款 |
一个常见的建模错误是把“待支付”直接指向“已出票”,跳过了“已支付”状态。出票的前提是支付完成,中间状态不能省。另一个错误是只画主路径,不画超时关闭、退款审核这类备选状态,导致测试用例设计时漏掉边界场景。状态图的价值其实在于逼你列出所有分支。
4.3 活动图:用泳道表达支付超时与异步出票
活动图适合表达业务流程中的并发和职责划分。订票支付就是一个典型场景:乘客提交支付后,系统同时启动“等待支付结果”和“支付超时计时”两个分支,先到先触发。
活动图里“泳道”按角色划分,每个人在任何泳道内的活动表示该角色负责的动作。订票支付活动图可以分三条泳道:乘客、系统、支付网关。乘客泳道里只有“提交支付确认”,系统泳道里有“创建支付请求”“开启超时计时”“接收支付回调”“更新订单状态”,支付网关泳道里有“处理扣款”“返回结果”。并发分支用一条粗横线分叉和汇合,分叉后两条支线并行,直到都到达汇合点才能继续。
活动图和流程图的区别就在这里:流程图只能一条线走到底,活动图可以表达并行分叉、汇合和职责分区。业务规则中有“同时做两件事”或“谁先完成谁决定走向”的描述时,优先考虑活动图。
4.4 UML中的动态结构图包括哪些:选图依据
如果你在准备软考中级,选择题里会直接考“UML 动态结构图包括哪几种”。答案是:时序图、通信图、状态图、活动图。其中通信图也叫协作图,与时序图同属交互图,只是表达重点不同。
| 表达目标 | 选用图 | 图的核心特点 |
|---|---|---|
| 消息发生的时间顺序是否严格 | 时序图 | 有生命线和时间轴,强调先后 |
| 对象之间消息往来的结构关系 | 通信图 | 无时间轴,强调对象间的连接 |
| 对象生命周期内的状态变化 | 状态图 | 有状态和事件,无状态跳转就是错的 |
| 业务规则里的并发与职责分配 | 活动图 | 有泳道、分叉、汇合 |
选图可以按一句话记忆:要交代前后次序选时序图,要交代关联结构选通信图,要交代状态变化选状态图,要交代流程并发选活动图。盯住你的问题是什么,别贪多把四张图全画一遍。
5. UML建模避坑指南:五类高频错误的成因与修正
画图看着自由,实际上每张图都有自己的边界和语法约束。下面这五个坑是我在评审建模设计时最常碰到的,按“现象 → 原因 → 解决”的顺序说清楚,照着自查能省不少事。
5.1 用例图画成操作菜单:这是最容易翻车的一类
现象:用例图里全是“点击查询”“输入乘机人”“点击支付按钮”这种椭圆,角色和用例之间连着一堆箭头,看起来像操作流程图。
原因:把“用户的动作”当成了“用户的业务目标”。点击按钮是实现细节,不是一个可以从外部观察结果的完整过程。
解决:写用例名时强迫自己回答“完成这件事用户得到了什么”。得到航班列表,叫“查询航班”;得到一张可退改的订单,叫“预订机票”;得到支付成功确认,叫“支付订单”。实现细节全部下沉到用例描述的主事件流里去。
5.2 类图关系乱用:箭头方向与基数标错
现象:所有有关联的类一律画实线普通箭头,或者全部画成依赖虚线;聚合和组合的菱形画在部分那一侧;一对多关系不标基数。
原因:对五种关系的语义没有形成判断链,画图时凭感觉,审图时也没有检查规则。
解决:按先生命周期、后引用强度来判断。整体销毁后部分仍在,选聚合;整体销毁部分一起销毁,选组合;只是方法里短暂用到,选依赖;长期持有引用,选关联。菱形画在整体类那一侧,基数必须成对出现,比如订单这一个整体侧标 1,明细那侧标 1..*。
5.3 时序图消息命名不规范:消息不是数据流
现象:时序图里的消息名称是“订单信息”“支付结果”“航班数据”这类名词短语,或者干脆用中文描述“把订单传给支付系统”。
原因:把时序图画成了数据流图,消息名直接沿用了业务名词,没有对应到真实的方法调用。代码落地的时候,这种消息名没法映射到接口,最后只能靠人肉理解。
解决:一律改成“动词 + 宾语 + 参数”的结构。查询航班信息就写 queryFlights(queryParam),创建订单就写 createOrder(orderInfo),支付回调就写 paymentCallback(result)。消息名和类图里的方法名保持同名词,时序图才能真正指导编码。
提示:评审时用手指着时序图上每个消息,问一句“这个背后的方法在哪个类里”,答不上来就要回头改。
5.4 状态图与活动图混搭:状态没有事件驱动
现象:状态图里画了菱形判断分支,活动图里出现“等待支付状态”“审核中状态”这种文字。
原因:把状态和活动混为一谈。状态是对象对外表现出的稳定可观测条件,必须由事件触发迁移;活动是系统执行的动宾短语,分支可以自动产生,不需要事件触发。
解决:画状态图时先列出所有事件名词,比如“支付成功”“用户取消”“出票完成”,再去搭图。没有事件参与的过程就不要在状态图里出现。画活动图时把“状态”这个词换成“动作”,比如“等待支付结果”是一个动作分支,不是状态。
5.5 图与图之间对不上:建模没有回查迭代
现象:用例主事件流写了 5 步,时序图画了 7 个消息;类图 Order 没有 orderStatus 属性,状态图里却有 5 个状态;活动图的支付分支和时序图的消息顺序完全脱节。
原因:每张图独立完成,完成后没有回查其他图。模型不是单张图的自洽,而是整套图同一个世界观的延续。
解决:三个回查点必须走一遍。第一,用例主事件流的每一步能在时序图里找到对应消息;第二,时序图的每个消息能在类图里找到对应的类和方法;第三,状态图里的状态值在类图里有对应字段,在时序图里有对应更新时机。我一般画完一张新图就回头扫一遍旧图,宁可多改两版,也不要交付时被评审连环追问。
6. 从模型到落地:用UML图反推数据库表与后端接口
建模的价值要落在代码里才算闭环。这里给出两条最常见的落地路径:由类图反推数据库表结构,由时序图反推后端接口列表。最后再提一个我常用的工具习惯。
6.1 由类图映射数据库表:一对多与多对多的处理
类图上的关联关系可以直接翻译成建表规则。一对多关系,在“多”方表里加一列外键,比如 OrderItem 表要带上 order_id 和 flight_id 两个外键;多对多关系必须建中间表,比如乘客和常旅客计划的关联就是一个独立关系表。组合关系意味着级联删除,订单表删掉时关联的明细表数据要清掉。
6.2 由时序图推导接口:消息顺序即接口清单
时序图的消息顺序就是接口调用的顺序,翻译时只需要把每个消息对应到 URL 上。
| 时序图消息 | 后端接口 |
|---|---|
| queryFlights(queryParam) | GET /api/flights?departure=PEK&arrival=SHA&date=... |
| createOrder(orderInfo) | POST /api/orders |
| paymentCallback(result) | POST /api/payments/callback |
| cancelOrder(orderId) | DELETE /api/orders/{orderId} |
6.3 用PlantUML把模型文本化,便于评审与版本管理
我现在的习惯是核心模型用 PlantUML 写,每次评审改的是文本 diff,而不是一张导出的 PNG 图。下面是一个简化版订单类图的写法:
@startuml class Order { - id: Long - status: Integer - totalAmount: BigDecimal + pay() : boolean + cancel() : boolean } class OrderItem { - flightId: Long - passengerName: String - cabinClass: String } Order "1" o-- "1..*" OrderItem : contains @enduml这段文本渲染出来的效果和手画类图完全一致,但它可以进 Git,改一行就知道改了什么。时间序列图、状态图也可以用同样方式文本化,比在画图工具里拖拽效率高很多,也和建模设计文档的版本管理天然兼容。
我现在的习惯是接到需求先把用例图和时序图理透,再谈类图和建表。这两张图不顺,类图画得再规范也是对空气建模。UML 的价值不在图本身,在于它逼你把业务边界、协作顺序、状态迁移和数据结构一次性想清楚。希望这套流程能帮你少掉几次坑,也希望帮到你。
本文还有配套的精品资源,点击获取