简介:《航空订票系统 UML 建模设计》是一份面向软件工程课程设计、毕业设计及系统分析建模学习者的文档型资料,围绕航空订票场景梳理从需求分析到多种 UML 视图的完整设计思路,可帮助读者理解订票、航班信息管理、个人信息管理三个核心模块的功能边界与建模方法。压缩包内含 1 个 docx 文件,大小约 535KB,正文以文字说明配合图形化建模内容呈现,适合直接查阅、对照修改或作为课程作业的结构参考。目前已有 1626 人浏览学习。内容覆盖用户、管理员和顶层用例图,用户类、管理员类、航班信息类、机票类与个人信息类等类图,以及登录、查询、预订、退票等状态图、顺序图和合作图;同时给出注册、登录、航班查询、机票预订、退票、修改信息、打印票据等用例规约。读者可据此梳理需求条目、补充图形细节,并用于课程设计报告撰写与答辩准备。
1. 航空订票系统这个题目,卡人的从来不是画图
航空订票系统这套 UML 建模设计,每年都有人选,也每年都有人栽在同一个地方:功能列了三十条,图也画了二十来张,答辩时被问一句「退票这个用例在类图里对应哪个方法」就卡住了。真正难的不是把用例图画得好看,而是让需求、用例、类、时序、状态这五份产物互相指认,形成一条能追溯的链。这份方案面向的正是这类交付:三类角色、十六个用例、五个核心类,从需求描述一路推到能落库的表结构和能对照检查的追溯矩阵,适合正在做软件工程课设或毕设、需要交一份完整建模文档的人直接照着搭。
2. 需求拆解与参与者划分:三类角色与十六个用例的边界
2.1 游客、注册用户、管理员:权限从首页那个入口就分岔了
这个系统的权限差异不在业务页面上,而在首页就分开了。未注册的游客只能访问首页,首页上只有两个可用入口——登录和前往注册,别的什么都点不到。登录成功后才会跳转到航班查询页面,这时才谈得上查询、订票、退票、付款、打印票据这一串动作。管理员就更特殊:他不是自助注册出来的,而是由系统的最高权限管理员派发标识符,或者用航空公司内部员工号来区别鉴定,登录入口和普通用户一样,但进入后的操作面完全不同。
这里有个容易被忽略的建模点:如果只画「游客」「注册用户」「管理员」三个参与者,「管理员注册」这个用例就没有合法的发起者了。通常的做法是补一个只出现在管理员注册场景里的第四类参与者,比如「超级管理员」,他唯一的职责就是审核并派发管理员标识符。不补这一个角色,管理员注册用例在图上就是孤悬的,权限模型也跟着塌一半。
权限分层建议先落成一张矩阵,再回头画用例图,这样谁该连哪条线一目了然:
| 功能面 | 游客 | 注册用户 | 管理员 |
|---|---|---|---|
| 浏览首页 | 允许 | 允许 | 允许 |
| 注册、登录 | 允许 | 允许 | 允许(入口相同) |
| 航班查询 | 不可见 | 允许 | 允许 |
| 机票预订、付款、退票、打印票据 | 不可见 | 允许 | 不允许 |
| 航班信息增删查改 | 不可见 | 不允许 | 允许,且需二次验证 |
| 订票数据统计 | 不可见 | 不允许 | 允许 |
| 个人信息查询与修改 | 不可见 | 允许 | 允许 |
矩阵里最容易被写漏的是最后那条管理员敏感操作。原文把这层校验描述得很具体——管理员点击「添加航班信息」或「修改航班信息」时,要再输入一次当初派发的标识符。这在 UML 里不该写成一句前置条件,而应该是一个被 include 的独立用例「管理员身份再验证」,因为它是一次完整的独立交互:弹窗、输入、校验、失败计数、超限锁定。把它画成前置条件,用例规约里就只能写一句「具备相应权限」,异常流全部丢失。
2.2 用例规约的骨架:前置条件、基本流、异常流缺一不可
十六个用例如果只用一张图交代,信息量是不够的,所以每个用例都要配一份规约。规约的字段结构是固定的:用例名称、用例 ID、角色、用例说明、前置条件、基本事件流(参与者动作与系统响应两列对照)、其他事件流、异常事件流、后置条件。ID 编到 16 号,正好覆盖三个模块:
| 用例 ID | 用例名称 | 主参与者 | 归属模块 |
|---|---|---|---|
| 1 | 用户注册 | 未注册用户 | 个人信息管理 |
| 2 | 用户登录 | 注册用户、管理员 | 个人信息管理 |
| 3 | 航班查询 | 注册用户、管理员 | 订票管理 |
| 4 | 机票预订 | 注册用户 | 订票管理 |
| 5 | 已订机票查询 | 注册用户 | 订票管理 |
| 6 | 退票 | 注册用户 | 订票管理 |
| 7 | 修改已订航班个人信息 | 注册用户 | 订票管理 |
| 8 | 查询个人信息 | 注册用户、管理员 | 个人信息管理 |
| 9 | 修改个人信息 | 注册用户、管理员 | 个人信息管理 |
| 10 | 打印票据 | 注册用户 | 订票管理 |
| 11 | 添加航班信息 | 管理员 | 航班信息管理 |
| 12 | 修改航班信息 | 管理员 | 航班信息管理 |
| 13 | 删除航班信息 | 管理员 | 航班信息管理 |
| 14 | 统计订票数据 | 管理员 | 航班信息管理 |
| 15 | 订票管理 | 注册用户 | 订票管理(包级) |
| 16 | 航班信息管理 | 管理员 | 航班信息管理(包级) |
15 和 16 这两个是包级用例,粒度比前面十四个粗一层,它们和具体用例之间是包含关系,不是并列关系。把它们和 1 到 14 并排画在同一层级上,通常会让用例图显得重复,一般画在顶层用例图里,具体用例图里不再重复出现。
以「机票预订」(ID 4)为例,规约的主体通常这样组织:
| 字段 | 内容 |
|---|---|
| 前置条件 | 用户已登录且停留在查询结果页面 |
| 基本流 | 用户点击预订按钮 → 系统读取该航班的剩余座位与该用户身份 → 生成订单并锁定一个座位 → 返回订票成功页面 |
| 其他事件流 | 用户取消确认弹窗,回到查询结果页,不产生订单 |
| 异常事件流 | 剩余座位为 0,提示该航班已售罄;同一用户对同一航班存在未支付订单,提示先支付或取消;用户会话已过期,跳转登录页 |
| 后置条件 | 订单状态为待支付,航班剩余座位减一 |
原文里「机票预订」「已订机票查询」「删除航班信息」几个用例的异常流写的是「无」。这在课设里不算错,但在答辩里一追问就露底:订满了怎么办、重复订怎么办、并发两个人抢最后一张怎么算。合格的写法是把这些边界在规约阶段就补齐,后面状态图里的守卫条件才有出处。退票(ID 6)也一样,基本流只有一句「系统根据订票信息 id 进行退票操作」,实际上退票要判定的条件至少有三条:订单是否已支付、航班是否已发出、是否已超过航司规定的退票时限。这三条条件写进规约的异常流,状态图里的迁移条件直接抄过来就行。
2.3 三张用例图的视角差异与 include、extend 的用法边界
用例图在这个系统里分三张:顶层用例图、注册用户用例图、管理员用例图。顶层图只放三个包级用例——个人信息管理、订票管理、航班信息管理,参与者是注册用户和管理员,用来交代系统的整体边界。注册用户用例图展开十个具体用例:注册、登录、航班查询、机票预订、已订机票查询、退票、修改已订航班个人信息、查询个人信息、修改个人信息、打印票据。管理员用例图展开八个:注册、登录、添加航班信息、修改航班信息、删除航班信息、查看个人信息、修改个人信息、统计订票数据。
include 和 extend 这两个关系是翻车重灾区。这个系统里真正适合用 include 的只有两处:一是「管理员身份再验证」被「添加航班信息」「修改航班信息」「删除航班信息」三个用例共同包含,因为它每次都是必走的一步;二是「身份验证」被所有需要登录后访问的用例包含。而「用户登录」本身不适合被 include 到航班查询、订票、退票里去——登录是这些用例的前置条件,不是每次执行都要重走一遍的步骤,画成 include 会让图上一堆箭头指向登录,看着密不透风,逻辑却重复。
extend 用在哪?「打印票据」在已订机票查询页面里是一个可选动作,只有当用户订过票、且订单已支付时才会出现,这种「有条件的可选扩展」适合用 extend,箭头由扩展用例指向基用例「已订机票查询」。判断标准很朴素:每次都要做的是 include,可能做也可能不做的是 extend,登录这种一次性的门槛写前置条件。
3. 静态结构建模:五个核心类怎么从用例表推出来
3.1 五种关系怎么用:泛化留给身份,关联留给业务
这个系统的静态结构由五个核心类撑起来:用户类、管理员类、航班信息类、机票类、个人信息类。原文的表述是「用户类包括注册用户和管理员两种角色」,这个说法在需求层面成立,但落到类图上要小心:如果直接把管理员当成用户类的一个属性值,管理员就自动继承了「订票」「退票」这些方法,权限模型立刻错乱,因为管理员在业务上根本不订票。
我一般会这样做:抽一个抽象的用户类,只保留身份认证和基本信息两块能力,注册用户类和管理员类都从它泛化出来,订票相关的方法只挂在注册用户类上,航班管理的方法只挂在管理员类上。这样既复用了登录、改密码这类公共行为,又把两个角色的能力边界切干净了。
其余四类关系的判断标准也要说清楚。注册用户和机票之间是一对多的关联,一个用户可以有多张订单,一张订单只属于一个用户;航班信息和机票之间也是一对多,一个航班对应多张票,这里的关键是机票要持有航班的编号而不是把整个航班对象塞进去,否则航班改期时历史订单会跟着变。管理员和航班信息之间,与其画成关联,不如画成带 «manage» 标记的依赖,因为管理员不「拥有」航班,只是操作它。个人信息类和用户类之间,按语义应该是组合关系——个人信息随账号存在而存在,账号注销,个人信息一并消失,画成普通关联在答辩时会被追问生命周期。
3.2 属性和方法:把用例里的动词一个个落到类上
类图最怕画成数据字典,只有属性没有方法。这个系统的做法是拿用例清单反查:每个用例名里的动词,都要在某个类上找到对应的方法。查询对应 query,预订对应 book,退票对应 cancel,付款对应 pay,打印对应 print,增删改对应 add、update、delete,统计对应 stat。找不到落点的用例,说明类拆得不够或者拆错了。
下面这段类骨架可以直接拿去改,字段名和后面建表脚本里的列名保持一一对应,方便答辩时指着图说「这个属性就是那张表的这一列」:
// 抽象身份类:只放认证与基础信息,不放业务动作 public abstract class User { protected String userId; // 用户唯一标识,注册时生成,全库唯一 protected String username; // 登录账号,对应 user 表的唯一索引 protected String passwordHash; // 口令摘要,禁止存明文 protected String realName; // 姓名,来自个人信息 protected String idCardNo; // 证件号,打印票据时使用 protected String phone; // 联系方式,退票通知用 protected int failCount; // 连续登录失败次数,达到 5 次锁定 15 分钟 public abstract boolean verifyCredential(String rawPassword); public abstract boolean changePassword(String oldPwd, String newPwd); } // 注册用户:订票相关动作全部落在这里 public class RegisteredUser extends User { public List<Flight> queryFlight(FlightQuery q) { /* 对应用例 3 */ } public Ticket book(Flight f) { /* 对应用例 4,内部要校验剩余座位 */ } public List<Ticket> listMyTickets() { /* 对应用例 5 */ } public boolean cancel(Ticket t) { /* 对应用例 6,需校验航班未发出 */ } public boolean pay(Ticket t) { /* 付款,成功后订单进入已支付 */ } public byte[] printTicket(Ticket t) { /* 对应用例 10,返回票据文件流 */ } } // 管理员:航班管理相关动作落在这里 public class Admin extends User { private String adminToken; // 派发的标识符,敏感操作二次校验用 public boolean addFlight(Flight f) { /* 对应用例 11,先校验 adminToken */ } public boolean updateFlight(Flight f) { /* 对应用例 12 */ } public boolean deleteFlight(String flightNo) { /* 对应用例 13,逻辑删除优先 */ } public StatResult statTicket(Date from, Date to) { /* 对应用例 14 */ } }这段代码里的几个参数值得单独说明。failCount不是随手加的,它对应登录用例的异常流「连续失败锁定」,没有这个字段,规约里的那条异常流就无法落地。adminToken单独放在管理员类而不是用户类,是因为它就是「管理员身份再验证」这个 include 用例的载体。cancel方法的注释里写了「需校验航班未发出」,这条校验就是后面状态图迁移上的守卫条件,图和代码在这里对上了。deleteFlight用逻辑删除而不是物理删除,原因是历史订单还引用着这个航班,删了订单就查不到航班信息,这是建模阶段就该定下的事。
机票类在这个系统里同时承担订单职责,字段设计上要把「票」和「订单状态」都装进去:订单号、用户编号、航班编号、乘机人姓名、证件号、联系电话、票价、支付状态、订单状态、下单时间、支付时间、退票时间。航班信息类则要有航班号、出发城市、到达城市、出发时间、到达时间、票价、总座位数、剩余座位数、航班状态。个人信息类如果和用户类合并,就把 realName、idCardNo、phone 三个字段留在用户类里;如果坚持独立成类,就用一对一组合关系关联过去,方法上只保留查询和修改两个,不要塞业务动作。
3.3 类图落到建表脚本:三张表把关系坐实
类图画完不落表,很容易变成一张漂亮的废图。这个场景下三张主表足够承载核心链路,注意看约束条件,它们和规约里的异常流是一一对应的:
-- 航班信息表:seat_left 的检查约束直接对应「售罄」异常流 CREATE TABLE flight ( flight_no VARCHAR(16) PRIMARY KEY, -- 航班号,业务主键 dep_city VARCHAR(32) NOT NULL, arr_city VARCHAR(32) NOT NULL, dep_time DATETIME NOT NULL, arr_time DATETIME NOT NULL, price DECIMAL(10,2) NOT NULL CHECK (price >= 0), seat_total INT NOT NULL, seat_left INT NOT NULL, flight_status VARCHAR(16) NOT NULL DEFAULT 'SCHEDULED', -- SCHEDULED/DEPARTED/CANCELLED CONSTRAINT chk_seat CHECK (seat_left >= 0 AND seat_left <= seat_total) ); -- 订单表:一张票一条记录,order_status 与状态图的五个状态严格同名 CREATE TABLE ticket ( order_id VARCHAR(32) PRIMARY KEY, user_id VARCHAR(32) NOT NULL, flight_no VARCHAR(16) NOT NULL, passenger VARCHAR(32) NOT NULL, id_card_no VARCHAR(32) NOT NULL, phone VARCHAR(16) NOT NULL, amount DECIMAL(10,2) NOT NULL, pay_status VARCHAR(16) NOT NULL DEFAULT 'UNPAID', -- UNPAID/PAID/REFUNDED order_status VARCHAR(16) NOT NULL DEFAULT 'PENDING', -- PENDING/PAID/TICKETED/CANCELLED/REFUNDED created_at DATETIME NOT NULL, paid_at DATETIME NULL, refunded_at DATETIME NULL, FOREIGN KEY (flight_no) REFERENCES flight(flight_no) ); -- 管理员敏感操作留痕:对应「管理员身份再验证」用例的失败与成功记录 CREATE TABLE admin_op_log ( log_id BIGINT PRIMARY KEY AUTO_INCREMENT, admin_id VARCHAR(32) NOT NULL, operation VARCHAR(32) NOT NULL, -- ADD_FLIGHT/UPDATE_FLIGHT/DELETE_FLIGHT target_no VARCHAR(16) NULL, result VARCHAR(16) NOT NULL, -- PASS/REJECT op_time DATETIME NOT NULL );三点说明。第一,chk_seat这条约束把「剩余座位不能为负」写进了数据库层,应用层再判一次,双保险,并发抢最后一张票的时候不至于出现负库存。第二,order_status的五个取值不是随便列的,它必须和后面状态图里画出来的五个状态完全同名同拼写,答辩时最容易被抓的就是这里对不上。第三,admin_op_log这张表在类图上通常对应一个操作日志类,很多人会漏掉它,但只要有「二次验证」这个需求,就必须有地方记录验证结果,否则审计这条线就是断的。
4. 动态行为建模:时序图、状态图、协作图各管一段
4.1 时序图:登录和注册单独建模,其余用引用框接进去
时序图不要一张图画完所有流程,那样每张都长得差不多,重点全糊了。这个系统的做法是先挑出被反复使用的模块——用户登录、用户注册、登录验证——单独画成独立时序图,其它时序图在第一帧放一个引用框指向它,需要看细节再翻过去。这样改一处只改一张图,不用满篇找。
登录时序图的生命线通常五条:参与者(用户)、登录界面、认证控制器、用户数据访问对象、数据库。消息顺序是:用户输入用户名和密码并提交;界面把凭证传给控制器;控制器先做非空校验,再按用户名查库;查到用户后比对口令摘要;成功则签发会话凭证并写登录日志,失败则计数加一,累计五次锁定账号。下面这段服务层实现就是这张时序图的文字版,一一对应:
def login(username, password): # 对应时序图上的第一个 alt 分支:非空校验 if not username or not password: raise BizError("用户名或密码为空") user = user_dao.find_by_username(username) # 生命线 3 -> 4 if user is None: raise BizError("用户名或密码错误") # 不区分账号不存在与密码错,避免枚举 if user.fail_count >= 5: raise BizError("账号已锁定,请 15 分钟后再试") if not verify_password(password, user.password_hash): user_dao.incr_fail_count(user.user_id) # 失败计数落库 raise BizError("用户名或密码错误") user_dao.reset_fail_count(user.user_id) token = issue_token(user.user_id, user.role) # 凭证里带角色,后续权限判定直接用 audit_log.write(user.user_id, "LOGIN", "PASS") # 对应 admin_op_log 的同构设计 return token参数上要留意两点:fail_count的阈值和锁定时长必须和用例规约、状态图里的账号锁定状态写成同一组数字,三处任意一处改了另外两处就得跟着改;issue_token里带上role,是为了让「权限验证」这个公共用例在实现层不必再查一次库,管理员点敏感操作时,控制器先看凭证里的角色,再看adminToken是否匹配,两级校验刚好对应类图上管理员类的那个字段。
注册时序图比登录多一步写库和一步唯一性校验:用户进入注册页提交信息,控制器校验用户名是否已存在、两次密码是否一致、手机号格式是否合法,全部通过后写入用户表,返回注册成功并跳转登录界面。这里的关键是把「两次密码不同」和「用户名已存在」都画成 alt 分支,不能只画成功路径。退票、订票、添加航班这几张时序图的第一帧都用一个引用框指向登录时序图,表示「此处已完成身份认证」,图面立刻清爽。
4.2 状态图:订单四态加航班三态,守卫条件写在迁移上
这个系统的状态图至少要画两张,一张管用户会话,一张管订单,航班状态可以并入订单图或者单独一张。用户登录状态很简单:未登录、已登录、锁定三态,未登录到已登录的迁移触发事件是「提交正确凭证」,未登录到锁定是「连续失败达到五次」,锁定回未登录靠「锁定超时」。
订单状态是重点,五个状态:待支付、已支付、已出票、已取消、已退票。迁移和守卫条件整理成表更清楚:
| 当前状态 | 触发事件 | 守卫条件 | 目标状态 | 落库动作 |
|---|---|---|---|---|
| (无) | 提交预订 | 剩余座位大于 0 且无同航班未支付订单 | 待支付 | 插入订单,座位数减一 |
| 待支付 | 完成支付 | 支付回调成功 | 已支付 | 写支付时间,支付状态置为已支付 |
| 待支付 | 超时或主动取消 | 下单超过 15 分钟,或用户点取消 | 已取消 | 座位数加一,释放库存 |
| 已支付 | 出票 | 无 | 已出票 | 生成票据编号 |
| 已支付、已出票 | 申请退票 | 航班状态为未起飞,且距起飞超过规定时限 | 已退票 | 写退票时间,座位数加一 |
守卫条件这一列是状态图真正的价值所在。原文里退票的前置条件只有一句「用户进入查询页面」,实际上「航班未发出」才是决定这个迁移能不能触发的关键判据,写成[flight.flight_status = 'SCHEDULED' and now() < dep_time - 2h]这样一条方括号里的条件,图和代码就绑在一起了。反过来,如果状态图上没有守卫条件,开发时大概率会把退票逻辑写成「点一下就退」,测试阶段才发现已起飞的航班也能退,这就是典型的模型没约束住实现。
航班状态单独画的话三态就够:待发布、已发布、已取消,已发布再流向已起飞、已到达。它和订单状态的联动点只有一个——航班一旦进入已起飞,所有关联订单的退票迁移全部失效,这条联动关系建议在状态图旁边用一行注释标出来,比藏在正文里强。
4.3 协作图与活动图:一个讲对象链接,一个讲分支并发
协作图(通信图)和时序图在语义上是等价的,区别只在强调什么。时序图强调时间顺序,协作图强调对象之间的链接结构,消息前面带编号,比如 1、1.1、2、2.1。这个系统里画两张协作图就够:一张是「用户订票与查票」,对象包括用户、查询界面、订单控制器、航班、订单、数据库,编号消息能清楚看出「先查航班,再基于航班建订单,最后写库」这个依赖链;另一张是「查询并修改个人信息」,对象少一些,重点展示界面、控制器、用户对象、数据库四者的链接。
活动图解决的是另一个问题:分支和并发。订票主流程里有两处必须用活动图说清楚。第一处是分支,查询结果为空时走「提示无匹配航班」,不为空才进入预订。第二处是并发,用户提交订单后,「锁定座位」和「发起支付」在语义上可以并行,但工程上必须串行——先锁座再支付,支付失败要回滚释放座位。用一个 fork 分叉出锁座与风控校验两条流,再用 join 汇合到支付,支付失败的分支连回「释放座位」,这张图一画,开发就不会把顺序写反。
活动图用泳道分成三条:用户、系统、支付网关。泳道划分的意义在于责任归属清晰,比如「写支付流水」这条动作只能落在系统泳道,不能落在支付网关泳道,否则实现时容易把不属于自己的职责推给对方。
5. 建模避坑:五个最容易在答辩现场翻车的点
5.1 把「登录」include 进每一个业务用例
现象是用例图上密密麻麻全是箭头指向「用户登录」,十几个用例每个都连一条虚线过来,图面几乎看不清。原因多半是把「必须登录才能用」误当成了「每次执行都要登录一次」。解决办法很直接:登录写成业务用例的前置条件,只有在同一张图里需要表达「这一步必须执行且被复用」时才用 include,比如「管理员身份再验证」。改完之后图上的 include 箭头通常不超过三条,清爽得多。
5.2 用例规约的异常流一栏写「无」
现象是十六份规约里有七八份异常流是空的,或者只写一句「系统提示错误」。原因是写的人只盯着正常路径,没想过边界。解决方法是拿每个用例问三句话:输入为空怎么办、资源不够怎么办、状态不允许怎么办。订票问座位够不够,退票问航班飞没飞,注册问用户名重不重复,添加航班问航班号是否已存在。这三问答完,异常流基本就满了,而且这些异常后面会原样变成状态图的守卫条件和代码里的校验分支,一份功夫三处用。
5.3 类图只有名词没有动作,退化成数据字典
现象是类图上每个框里只有属性列表,一个方法都没有,或者方法全是 get、set。原因是画图时对着数据库字段抄。解决办法是拿用例清单逐个反查:十六个用例里的动词,必须能在类图上找到同名或近义的方法,找不到就说明类拆错了。查一遍通常会发现「统计订票数据」这个用例没有落点,于是补一个统计服务类或者把它挂到管理员类上,类图立刻就有了行为。
5.4 时序图的生命线画成页面控件
现象是时序图顶上一排生命线写着「按钮」「输入框」「下拉框」,消息在控件之间飞来飞去。原因是把界面原型直接搬进了时序图。解决办法是把生命线收敛到四类对象:参与者、边界类(界面)、控制类(控制器或服务)、实体类(领域对象与数据访问)。一个页面最多出现一个边界类,控件属于页面内部细节,不该出现在这一层。改完之后,一张图的宽度能缩掉三分之一。
5.5 状态图和用例规约各说各话
现象是规约里写「航班未发出可退票」,状态图上退票迁移却没有守卫条件,代码里又变成「已支付就能退」,三处三个说法。原因是没有把状态名、条件名统一成一份词表。解决办法是先定一份状态词表,五个订单状态名、三个航班状态名、四条守卫条件名,全部写死,规约、状态图、建表脚本、代码注释只允许引用词表里的词,不允许临时造新词。这份词表放在文档最前面,答辩时被追问任何一个状态,翻到同一页就能对上。
6. 验收与答辩:用一张追溯矩阵把五套图串起来
建模文档写完之后,最后一道工序不是排版,是自查一致性。我习惯做一张追溯矩阵,横着放五列——用例 ID、类与方法、时序图编号、状态迁移、数据表字段,竖着把十六个用例全列一遍,每行填满才算过关。填不满的行就是漏洞,答辩老师十有八九会挑那些行来问。
| 用例 | 类.方法 | 时序图 | 状态迁移 | 表字段 |
|---|---|---|---|---|
| 用户登录 | User.verifyCredential | 登录时序 | 未登录 → 已登录 | user.password_hash |
| 航班查询 | RegisteredUser.queryFlight | 查票协作图 | 无 | flight.dep_city、arr_city |
| 机票预订 | RegisteredUser.book | 订票协作图 | 空 → 待支付 | ticket.order_status |
| 退票 | RegisteredUser.cancel | 退票时序 | 已支付 → 已退票 | ticket.refunded_at |
| 添加航班信息 | Admin.addFlight | 添加航班时序 | 无 | flight.flight_no |
| 统计订票数据 | Admin.statTicket | 无 | 无 | ticket 聚合查询 |
填这张表的时候有三个自检点。第一,同一个动作在四张图里只能有一个名字,比如「退票」不能在一处叫 cancel、另一处叫 refund、代码里又叫 deleteOrder,取名不统一是答辩现场最容易被抓住的把柄。第二,每个状态迁移都必须有守卫条件,没有守卫条件的迁移要么补条件,要么删掉,因为无条件迁移意味着业务上无人约束。第三,时序图上的每条消息都要能在类图上找到对应的方法,找不到就说明时序图画出了系统根本不具备的能力。
答辩陈述也有技巧。不要从第一张图开始按顺序讲,那样十分钟讲不完还平淡。挑一条完整链路走到底:游客进首页注册、登录、查到一个航班、下单、支付、查看已订机票、退票。走这条链路的时候,每经过一张图就停下来二十秒,指着图说清楚「这里的用例 ID 是 6,对应类图里 RegisteredUser 的 cancel 方法,状态图上是从已支付到已退票的那条迁移,守卫条件是航班未起飞」。四张图串成一条线,评委记住的是你的逻辑闭环,不是图的数量。
还有一个细节值得提前准备:把所有图里的名字(用例名、类名、方法名、状态名)复制出来做一次全文查找,确认没有同义不同名的情况。我吃过这个亏,退票在用例图里叫「退票」,在状态图里写成「退订」,被追问了五分钟才解释清楚是一回事。从那以后我每次交建模文档前都强制走一遍全名词表核对,哪怕时间再紧也不跳过。希望帮到你。
本文还有配套的精品资源,点击获取