简介:航空订票系统UML建模设计文档,面向软件工程课程设计、系统分析与设计方向的学习者,完整呈现从需求调研到多视角建模的落地思路。资源为单个docx文档,共535KB,以文字说明结合UML模型的方式,集中描述订票模块、航班信息管理模块和个人信息管理模块三大核心功能。文档依次给出用户、管理员、顶层三类用例图,并绘制类图明确用户、管理员、航班信息、机票、个人信息等核心类及其操作;状态图覆盖登录、航班查询、机票预订、退票等关键状态,顺序图与合作图进一步展示注册、登录、查询、预订等典型交互过程,同时附有细化到事件流和异常流的用例规约,便于读者直接套用。已有1622人学习下载,适合需要完成UML建模作业、课程设计或希望按完整流程练习系统设计的读者参考。
1. 航空订票系统UML建模设计:画图只是手段,模型自洽才是交付物
做一份航空订票系统的UML建模设计,大部分人第一反应是打开画图工具把用例图、类图、时序图各画一张交差。实际评审时,真正被追问的往往不是某张图画得漂不漂亮,而是用例与类图之间的职责对不对得上、订单状态机漏没漏状态、时序图里消息能不能在类图上找到对应方法。这套建模的难点,不在UML语法,而在把航空订票这个业务域拆成边界清晰、互相咬合的对象模型。读完这篇,你能从用例出发,按一条可复现的路径把边界、静态结构、动态行为依次铺开,并拿到一份能直接落地到代码的检查清单。适合正在做课程设计或想把需求转成正规模型的从业者和学生。
2. 用例视角先于类图:航空订票系统的边界和角色怎么定
2.1 参与者和系统边界:把外部系统挡在外面
航空订票系统的用例建模,第一件事不是列功能清单,而是先回答两个问题:谁在使用这个系统?系统的边界到哪里?
常见做法是列出三类核心参与者:
| 参与者 | 定位 | 典型诉求 |
|---|---|---|
| 乘客 | 外部直接用户 | 查航班、订票、支付、退改签 |
| 票务员 | 内部操作人员 | 代客下单、处理特殊票务 |
| 管理员 | 系统维护者 | 维护航班计划、舱位和票价规则 |
还有一个特殊参与者容易被漏掉:支付网关。它在UML里同样作为外部参与者出现,因为支付动作虽然是在订票系统里发起,但真正完成资金流转的是外部系统。把支付网关画进参与者区,能避免后面对接时序图时出现“消息发给谁”的尴尬。
系统边界这条线要果断。值机、行李托运、机上服务这些属于机场运行和航空公司服务域,如果不是题目明确要求,不要吞进订票系统里。边界画大了,用例图会膨胀成流程图;边界画小了,退票、改签这些业务又无处安放。我一般会画一条虚线把参与者与用例隔开,边界的判断标准就一句话:这些动作是不是由订票系统自己完成的。系统只负责生成订单、锁座、出票和退款,其余全部视为外部依赖。
2.2 用例拆解:哪些功能必须画进用例图
用例不是菜单,一个用例必须对应一个完整的“角色可感知的目标”。航空订票系统里最核心的用例就五个左右,不建议强行凑十多个。下面这组是经过裁剪后比较稳定的集合:
| 用例 | 参与者 | 说明 |
|---|---|---|
| 查询航班 | 乘客、票务员 | 按出发地、目的地、日期检索可售航班 |
| 预订机票 | 乘客、票务员 | 创建订单、锁定座位,主流程核心 |
| 在线支付 | 乘客 | 被预订机票包含,通常不单独使用 |
| 取消订单 | 乘客、票务员 | 支付前可取消,释放座位 |
| 退票/改签 | 乘客、票务员 | 出票后对客票做变更,触发退款或换开 |
| 航班信息维护 | 管理员 | 维护航班计划、舱位和运价 |
用例之间的关系,评审时最容易被挑刺。我的经验是记住两条硬规则:include 表示主用例中“每次都会执行”的子流程;extend 表示“满足条件才触发”的可选分支。预订机票和在线支付就是典型的 include 关系:只要预订成功,必然走到支付,除非用户主动放弃;退票则是预订机票的 extend,因为它只有在出票后且符合退票规则时才被触发。
画图时不要追求炫技,把 include/extend 用在真正需要的地方即可。用力过猛的用例图,关系箭头比用例还多,读者反而看不清业务主线。
2.3 用例规约:评审时真正被逐字读的部分
用例图只能表达“有什么”,真正的业务规则都写在用例规约里。一份拿得出手的航空订票系统建模文档,用例图之下必须给关键用例配规约表,否则后期画类图和时序图时根本没有依据。
下面是我常用的规约模板,直接套用即可:
| 字段 | 内容 |
|---|---|
| 用例名称 | 预订机票 |
| 参与者 | 乘客、票务员、支付网关 |
| 前置条件 | 用户已登录,航班存在且有余票 |
| 主流程 | 1. 乘客选择航班和舱位 2. 系统校验余票并锁定座位 3. 系统创建订单,状态置为待支付 4. 乘客发起支付,支付网关返回成功 5. 系统生成客票,订单状态变为已出票 |
| 扩展流程 | 4a. 支付超时或失败,系统释放锁定的座位,订单变为已取消 |
| 后置条件 | 订单和客票数据落库,座位状态变为已售 |
填写规约时最需要花心思的是主流程里的动词:查询、锁定、创建、支付、生成。这些动词在后续类图里会直接变成方法名,在时序图里会变成消息名。如果规约里写的是“用户点击确认按钮”,类图上就会莫名其妙冒出 Button 类,这是典型的建模污染源。
3. 类图建模:航班、订单、乘客之间的静态关系
3.1 识别候选类:从用例规约里找名词
类图不是凭空想象的,它的素材来源就是用例规约中的名词和动词。把第 2 章预订机票规约里出现的名词过一遍:航班、舱位、订单、乘客、支付、客票,这六个名词就是最稳定的候选类。
其中最容易翻车的是“航班”这个概念。航空订票系统里,“航班”和“航班实例”必须区分开。航班是计划概念:某条航线每天一班,由机型、起降时刻决定;航班实例是某一天真正执行的那一班,它有具体的执行日期、余票数和当前状态。建模时如果只有一个 Flight 类,当日班次和航线计划就会混在一起,余票和座位就没地方挂。常见做法是拆成 Flight(航线计划)和 FlightInstance(航班实例)两个类:
| 候选类 | 关键属性 | 职责 |
|---|---|---|
| Flight | 航班号、出发地、目的地、起降时间 | 描述固定航线计划 |
| FlightInstance | 执行日期、状态、可售座位数 | 描述某一天的具体班次 |
| FlightSeat | 座位号、舱位等级、是否可售 | 与航班实例关联,一个班次对应多个座位 |
| Order | 订单号、下单时间、订单状态 | 承载预订主流程 |
| Passenger | 姓名、证件号、联系方式 | 乘机人信息 |
| Ticket | 票号、价格、乘机人 | 出票后生成的客票凭证 |
| Payment | 支付单号、金额、支付渠道 | 记录支付结果 |
3.2 类之间的关系:聚合、组合和关联别用错
类与类之间的关系,评审时最容易问“为什么这里是聚合而不是组合”。判断标准很简单:如果子类离开父类后没有任何独立存在的意义,就用组合;如果子类可以单独存在,只是被父类管理,就用聚合。
航空订票系统里最典型的组合关系有两处:Order 与 Ticket 是组合,一张订单里如果一张客票都没有,那订单本身就是残缺的;FlightInstance 与 FlightSeat 也是组合,班次取消后,这些座位记录失去意义。而 Passenger 与 Order 之间只是关联,一个乘客即使没有订单也照样存在。Flight 与 FlightInstance 则是聚合,航线计划删除了,某个历史日期的班次记录仍然可以作为审计数据存在。
多重性也要标清楚。Order 对 Ticket 是 1 对 1..,意思是订一张机票至少产生一张客票、最多产生多张;Passenger 对 Order 是 1 对 0..,表示一个乘客可以没有订单,也可以有多笔历史订单。这些数字是后续设计数据库表时外键关系的直接来源,漏标任何一个,表结构设计时就得返工。
3.3 用文本化UML工具把类图固定下来
类图画到文本工具里,既方便版本管理,也方便后期改动。下面的例子是一个最小可用的类图描述,覆盖了实体、属性和方法:
@startuml class Passenger { - passengerId: Long - name: String - contactPhone: String + createOrder(flightInstanceId: Long): Order } class FlightInstance { - flightDate: Date - status: String + getAvailableSeatCount(): int } class Order { - orderId: Long - orderTime: Date - status: OrderStatus + pay(): boolean + cancel(): boolean } class Ticket { - ticketNo: String - price: BigDecimal + issue(): boolean } class Flight { - flightNo: String - departure: String - arrival: String } Passenger "1" -- "0..*" Order Order "1" *-- "1..*" Ticket Order "1" --> "1" FlightInstance Flight "1" -- "0..*" FlightInstance @enduml这段描述里需要注意两点。一是方法不要只写 getter/setter,要体现业务动作:Order 类里出现 pay() 和 cancel(),正是因为用例规约的主流程里有“支付”和“取消”这两个动词;二是关系行里的多重性写在了两端,Passenger 端是 1,Order 端是 0..*,评审时通过这一行就能看出一个乘客能拥有多少订单。
方法签名里的参数也别乱给。createOrder(flightInstanceId: Long) 表示创建订单时只需要告诉系统哪个航班实例,不需要传入整个对象,这是降低类之间耦合的习惯做法。属性类型统一用 Long、BigDecimal、Date 这种基础类型,不要直接在类图里写自定义对象作为属性类型,那会掩盖真正需要的关联关系。
4. 动态建模:时序图、活动图和状态图的分工与配合
4.1 时序图:把“谁先调谁”钉死在一条时间线上
时序图回答的是“一个用例如何通过对象间的消息协作完成”。航空订票系统里最重要的时序图就是预订机票主流程,它的消息顺序直接对应用例规约的主流程编号。下面是最小可用版本:
@startuml actor Passenger participant "BookingSystem" as System participant "PaymentGateway" as Gateway Passenger -> System: searchFlight(dep, arr, date) System -> FlightInstance: findAvailableSeats() FlightInstance --> System: availableSeats Passenger -> System: createOrder(flightInstanceId, passengerId) System -> Order: create() System -> Gateway: requestPayment(orderNo, amount) Gateway --> System: paymentResult(success) System -> Ticket: issue(orderNo) Ticket --> System: ticketNo System --> Passenger: bookingSuccess(ticketNo) @enduml这张时序图的信息量远比看起来大。消息名称和类图方法一一对应,searchFlight 对应查询用例,createOrder 对应 Passenger 类里的工厂方法,issue 对应 Ticket 的成员方法。这意味着时序图里的每一条实线消息都应该能在类图上找到接收者的方法签名,找不到就说明类图或时序图有一方画漏了。
参数说明:requestPayment(orderNo, amount) 里只传订单号和金额,不传整个 Order 对象,这是与外部系统交互的安全习惯;paymentResult(success) 用返回值表示支付结果,实际项目中建议再增加一个状态码字段,方便表示“余额不足”“超时”等细分情况,但建模阶段不必过度细化。
时序图还隐藏着对创建与删除的建模:Order 和 Ticket 实例是在消息执行过程中被创建的,一般不画显式 create 图标,而是通过 createOrder() 和 issue() 这类方法名隐含表达。读者看到消息到达 Order 时,应该能脑补出“订单对象在这一刻诞生”。
4.2 活动图:用泳道和并发把业务分支摊开
活动图适合表达“流程中哪些步骤可以并行、哪些分支需要决策”。航空订票系统的支付与锁座就是个典型并发场景:用户在支付的同时,系统必须保证座位不被别人订走。这个并发约束用文字描述容易绕,画成活动图一眼就清楚。
常见的活动图结构是横向泳道:乘客、订票系统、支付网关各占一条道。流程从乘客选择航班开始,随后进入一个 Fork 节点,左侧分支执行“锁定座位”,右侧分支执行“请求支付”,两个分支结束后在 Join 节点汇合,再根据支付结果走两条分支:成功则生成客票并结束,失败则释放座位。这套结构能直接说明一个隐藏需求:锁座和支付是可以并行发生的,但出票必须在支付成功之后。
画活动图时注意两点。第一,泳道的数量不要超过系统参与者数量,别把数据库、消息队列这些实现细节画进去,活动图只表达业务层;第二,Join 节点两侧的分支必须一一对应 Fork 发起的分支,否则图上会出现“等一个永远不会来的消息”的悬空汇合,这是评审老师最爱抓的硬伤。
4.3 状态图:订单状态机是后期最容易返工的部分
订单状态图在航空订票系统里是最有价值的动态模型,因为它承载了几乎所有业务规则。一个订单从创建到结束,要经历待支付、已出票、已取消、已退票、已完成这五个状态。状态机写成文本工具后的样子如下:
@startuml [*] --> PendingPayment : createOrder() PendingPayment --> Paid : paymentSuccess() PendingPayment --> Cancelled : timeoutCancel() Paid --> Used : checkIn() Paid --> Refunded : applyRefund(meetPolicy) Refunded --> [*] Used --> [*] Cancelled --> [*] @enduml这里最关键的是每个迁移后面都要跟触发事件。paymentSuccess() 是事件,不是动作,它描述“支付成功这个事实发生之后,订单从待支付迁到已支付”;applyRefund(meetPolicy) 里带了一个守卫条件,表示只有满足退票政策时才能迁移,这个守卫条件就是用例规约扩展到退票分支时的依据。
状态图也是最容易暴露建模范式缺陷的地方。常见的漏写有两个方向:一是漏掉超时取消这个状态,只画了“用户主动取消”;二是没有把退票后的资金流状态表达出来,比如“已退票”之后还有一笔退款单处于“退款处理中”。把这两个补上后,订单状态机才算闭环。
5. 航空订票系统UML建模避坑:5个高频翻车点与排查思路
5.1 用例图画成流程图,系统边界被业务细节冲垮
现象:用例图里全是“点击查询按钮”“输入验证码”“跳转到支付页”,一张用例图画了二十个节点,活脱脱一份页面跳转说明。原因:把 UI 交互流程当成业务目标,建模时没有先区分“用户要什么”和“用户怎么操作”。解决:画用例前先做个动作转换,把“点击”类动词全部过滤掉,只保留“查询航班、预订机票、退票”这种以业务结果结尾的目标动词。如果一张用例图里出现超过十个用例,先考虑是不是把系统边界外的内容吞进来了。
用例图也不是画完就完,要回到第 2 章的角色清单逐一比对。每个参与者至少要有一个可达的用例,否则这个角色在系统里就没有存在价值。反过来,每个用例至少要有一个参与者能触发,否则这个用例就是悬空功能。
5.2 类图只有属性没有方法,状态图又是孤立状态
现象:类图每个类下面堆了十几个属性,方法区却干干净净;同一张文档里的订单状态图只有“待支付”和“已出票”两个状态,中间迁移靠箭头硬画。原因:照着数据库表设计抄属性,又凭感觉拼凑状态,没有从动态模型反推静态职责。解决:把时序图里的全部消息名收集起来,按接收者归组,直接补成类图的方法签名。订票主流程里 createOrder、pay、issue 这些消息落到 Order 和 Ticket 上,方法区就自然充实了。状态图则反过来做:先把订单从创建到归档可能经历的所有事件列成清单,按事件去推导状态迁移,而不是自己脑补状态列表。
5.3 同一份文档里,图与图之间互相矛盾
现象:用例规约的主流程里有“系统校验余票”,时序图里却没有任何一个对象发出过校验余票的消息;类图里没有 Payment 类,活动图里却画了支付网关的泳道。原因:分头画图,画完没有做交叉核对,把每一张图都当成独立交付物。解决:建立追踪关系表,把用例主流程的每一句话编号,对应到时序图的每一条消息,再对应到类图的每一个方法。在交付文档里放一张“用例-时序-类”追踪表,不通过的那几条就是矛盾所在。这个习惯能避免大部分评审现场被问倒的情况。
5.4 退票、改签、航班取消这些规则,模型里一个字没有
现象:用例图里看不到退票,时序图里看不到退款流程,状态图里没有航班取消迁移。原因:建模时只对着“正常订票路径”画图,把异常分支全部丢给“反正系统有按钮”这种口头承诺。解决:建模前先写业务规则清单,把退票时限、航班取消自动退款、超时未支付自动取消这三条硬规则贴在画图工具旁。每画完一张图就对照一次,规则没有体现就返工。建模文档的价值恰恰体现在这些异常分支里,正常路径没有任何建模难度,异常路径才是评审和后续开发的看点。
5.5 不建模型直接写代码,然后把图补成“事后诸葛亮”
现象:先写代码再画 UML,类图和代码完全一致,模型对设计没有任何指导意义。原因:把建模当成交作业的格式要求,没有理解模型是用于在编码前暴露设计问题的工具。解决:至少在实体关系、订单状态机、主流程时序这三个层面先建模再编码。代码写完后回头微调模型是允许的,但顺序反了,UML 就失去了存在的意义。正常情况下建模阶段要能把系统的高危分支找出来,比如“支付超时后座位怎么释放”这一类问题,等代码写完再发现只能重构。
6. 模型验证与落地:把UML建模结果落成可评审的docx与代码
6.1 用交叉检查清单把模型钉在一起
模型完成后,建议花半小时做一轮检查,比之后被评审问倒划算得多。我常用的检查清单如下:
| 检查项 | 涉及图 | 不通过时的常见表现 |
|---|---|---|
| 每个参与者都有可达用例 | 用例图 | 某角色画了但没有一条线连到用例 |
| 用例规约主流程有序号可追踪 | 用例图、时序图 | 时序图消息和规约步骤对不上 |
| 每条时序消息能找到接收方法 | 时序图、类图 | 类图方法区空白或消息接收者不存在 |
| 每个状态迁移有触发事件 | 状态图 | 只有箭头没有事件标注 |
| 异常分支有对应的状态或用例 | 用例图、状态图 | 退票/取消/超时在模型里无体现 |
检查时按表格逐行过,别跳。前面 5 章里提到的坑,大概率会在某一行的“不通过”里重新出现一次。这种检查不是给别人看的,是给自己返工用的。
6.2 从模型到代码与docx的落地习惯
模型通过检查后,落地路径已经非常明确了。类图直接对应实体类和仓储层,创建订单的方法签名可以直接抄成后端的应用服务接口;时序图对应服务编排层,消息顺序就是方法调用顺序;状态图可以用状态模式或一张状态机表驱动,把 PendingPayment、Paid、Refunded 这类标记定义成枚举常量,触发事件就是状态流转的方法名。
交付文档的组织顺序也有讲究。我会把 docx 按“用例视角→静态结构→动态行为→规则清单→一致性检查记录→后续实现映射”的次序排列,每张图下面紧跟对应的规约表或检查表,不要把所有图堆在前面,文字全放最后。读者翻到用例图时,下一页就该看到用例规约,这样才能直接理解建模的依据。
回看自己做过的几个建模项目,最值的投资就是画完图之后那半小时的交叉检查。曾经有一个订单状态图漏了“航班取消自动退款”这个迁移,导致后续实现阶段补了一整套补偿流程,改动量比预期多出近一倍。从那以后,每份建模文档交付前,我都会亲自对照检查清单过一遍,哪怕只是把迁移事件补全一个词。这个习惯能省下的返工时间,远超过画图本身。希望帮到你。
本文还有配套的精品资源,点击获取