ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

餐厅系统课程设计:用UML用例图、类图与包图驱动架构落地

餐厅系统课程设计:用UML用例图、类图与包图驱动架构落地 简介这是一份面向高校软件工程、计算机相关专业学生的UML课程设计文档围绕餐厅系统的架构设计展开适合正在做课程设计或需要参考建模流程的初学者与教师。文档共1个doc文件压缩包约262KB内容以文字与图表说明为主结构紧凑便于阅读。其中完整给出了课程设计题目要求、成员分工与章节安排依次覆盖需求分析、用例图、事件流文档、活动图、类图、数据库逻辑模型以及顺序图等动态建模环节并以顾客、服务员、厨师、收银员四类参与者为主线串联点菜、改单、查询、通知厨师与收银结算等业务。读者可借此了解从需求细化到类图提取、逻辑模型优化直至交互建模的完整推导过程直接用作课程设计范本、建模思路参考或课堂讲解素材。目前已有1008人学习下载适合想要快速理清UML各视图之间对应关系与建模顺序的读者参考借鉴。1. 餐厅系统课程设计为什么要先画 UML 再谈架构餐厅系统这个题目看着小做起来最容易失控点餐、加菜、退菜、并台、转台、会员折扣、后厨分单、日结报表需求像滚雪球。常见的翻车路径是先建库建表写到一半发现少了一张桌台状态表回头改类、改接口、改文档最后通宵补图图和代码还各说各话。更省事的顺序恰好反过来用 UML 用例图锁住系统边界用类图定住领域名词用包图决定代码往哪个目录放架构设计文档只是这些图加上约束说明的文字化产物。「uml用例图」「uml类图」「uml包图」这几个搜索词在课程设计里被反复搜本质上对应三次不可跳过的决策谁在用系统、系统里有哪些东西、这些东西怎么分堆。Visio 拖拽能应付答辩排版但如果要边写代码边改图PlantUML 这类文本化方案更划算图能进 Git、能 diff、能批量出图。放进同一个.doc里的内容也不该是图的堆砌而是图后面那套分层规则和追踪关系。2. 餐厅系统的用例图与需求边界从点餐到结账的角色建模用例图画得对不对直接决定后面类图有没有漏掉实体。判断标准很朴素图上每个用例都应该能对应一段可验收的业务目标而不是一个界面按钮。2.1 参与者与用例粒度先分角色再谈功能餐厅场景的参与者分成两类一类是坐在系统外边的人或外部系统一类是系统内的模块。外部系统也算参与者这一点最容易被漏等到画时序图时才发现支付回调、厨显推送没有归属。参与者类型核心目标主要用例顾客人点菜、结账浏览菜单、扫码下单、自助结账服务员人代客下单、处理异常开台、提交订单、加菜退菜、催单、转台并台厨师人获取并完成出餐接单、出餐确认、沽清收银员人收款、对账结账、折扣审批、交班对账门店经理人掌握经营日营业报表、菜品销量分析支付网关外部系统完成扣款发起支付、支付结果回调厨显系统外部系统展示待做菜品推送出餐单用例命名的粒度要统一成「动词 名词」并且一个用例只做一件事。「提交订单」是把一桌菜一次性给厨房而「输入手机号」只是「提交订单」主成功场景里的一步把它写成用例类图里就会凭空多出一个没有业务含义的类。数值边界也要在需求阶段问清楚一桌最多几个菜、并台允许几张桌、支付超时几分钟自动撤单这些数字后面都会变成状态图上的守卫条件。2.2 用 PlantUML 画餐厅用例图的最小脚本Visio 适合最终排版迭代阶段我更倾向文本脚本改一个角色名不用重新拖线。下面这段可以直接存成usecase.puml渲染startuml left to right direction skinparam packageStyle rectangle skinparam actorStyle awesome actor 顾客 as C actor 服务员 as W actor 厨师 as K actor 收银员 as Cash actor 支付网关 as PAY 外部系统 actor 厨显系统 as KDS 外部系统 rectangle 餐厅管理系统 { usecase 浏览菜单 as UC1 usecase 提交订单 as UC2 usecase 加菜/退菜 as UC3 usecase 催单 as UC4 usecase 出餐确认 as UC5 usecase 结账收款 as UC6 usecase 转台/并台 as UC7 usecase 日营业报表 as UC8 usecase 身份认证 as UC9 } C -- UC1 C -- UC2 W -- UC2 W -- UC3 W -- UC4 W -- UC7 K -- UC5 Cash -- UC6 Cash -- UC8 UC2 .. UC9 : include UC6 .. PAY : include UC5 -- KDS : extend 已出餐 enduml脚本里rectangle表示系统边界参与者必须画在矩形外面这是用例图最基本的语义参与者不属于系统内部。include用在必然发生的复用逻辑上比如任何写操作都要先认证extend用在可选或条件触发的扩展上比如出餐后需要通知厨显。这两个箭头方向相反画反了答辩时会被直接问住。left to right direction和skinparam只影响排版不影响语义图变宽时优先调这两个参数而不是去改用例划分。2.3 把「提交订单」写成用例规约表用例图只是目录真正能验收的是规约。给每个核心用例补一张表字段固定写起来快也方便后期对照测试字段内容用例名提交订单主参与者服务员前置条件桌台状态为「空闲」或「就餐中」菜品未沽清主成功场景1 选桌台 2 选菜品与数量 3 校验库存 4 生成订单 5 推送厨房 6 返回下单成功扩展流3a 库存不足提示缺货并回退到第 2 步4a 桌台被并发占用提示转台后置条件订单状态为「已提交」库存预占成功厨房收到出餐单业务规则单桌菜品不超过 50 道折扣需收银员权限扩展流那一栏是重点很多同学只写主流程结果状态图里凭空出现一个「库存不足」状态时序图也没有对应的备选分支。把扩展流写全后面三张动态图基本就是照抄。3. 类图与包图餐厅系统静态结构怎么落到代码目录静态图解决两个问题系统里有哪些对象、这些对象按什么规则分堆。类图决定能不能写出干净的实体类包图决定半年后接手的人能不能一眼看懂代码放哪。3.1 从名词短语抽类餐厅场景的类清单做法很土但有效把用例规约里出现的名词全部列出来再逐个判断它是否承载数据。类名关键属性关键方法来源用例Table桌台tableId、seats、statusoccupy()、release()、mergeWith()开台、转台并台Order订单orderNo、status、totalAmountaddItem()、submit()、cancel()提交订单OrderItem订单明细dishId、qty、unitPricesubtotal()提交订单Dish菜品dishId、name、price、stockisAvailable()、deduct()浏览菜单、沽清Category分类categoryId、name、sort—浏览菜单Payment支付单payNo、channel、amount、statuspay()、refund()结账收款Member会员memberId、phone、leveldiscountRate()折扣审批KitchenTicket出餐单ticketNo、orderNo、statedispatch()、finish()推送到厨房「订单管理」「库存服务」这类名词不是实体它们没有需要持久化的属性应该被识别为应用层服务硬塞进类图会让一张图变成两套语义混在一起。3.2 关联、聚合、组合、依赖在订单场景里的分界关系画错代码里的级联删除和外键就会跟着错。关系餐厅场景示例判断依据代码体现组合Order — OrderItem明细脱离订单没有意义子对象随父对象一起删除聚合Category — Dish分类删除后菜品仍可存在弱引用可置空关联Table — Order订单结束后桌台依然存在外键不级联删除依赖OrderService → InventoryService只在方法调用期使用参数或局部变量泛化Payment ← CashPayment、OnlinePayment同一接口的多种实现抽象类或接口多重性同样要标一桌可以有多张历史订单但同一时刻只允许一张有效订单所以是Table 1 -- 0..* Order。这张关系表配合下面的类图脚本就能把静态结构固定下来startuml class Table { -Long tableId -Integer seats -TableStatus status occupy() release() } class Order { -String orderNo -OrderStatus status -BigDecimal totalAmount addItem(Dish, int) submit() cancel(String reason) } class OrderItem { -Integer qty -BigDecimal unitPrice subtotal() : BigDecimal } class Dish { -Long dishId -BigDecimal price -Integer stock } class Payment { abstract -String payNo -BigDecimal amount pay() : PayResult refund() : PayResult } class CashPayment class OnlinePayment Table 1 -- 0..* Order : 关联 Order 1 *-- 1..* OrderItem : 组合 OrderItem 0..* -- 1 Dish : 关联 Payment |-- CashPayment Payment |-- OnlinePayment Order 1 -- 0..1 Payment : 关联 enduml*--是组合o--是聚合--是普通关联|--是继承。这些符号在 PlantUML、Visio、StarUML 里含义一致换工具不会走样但线型和箭头千万别凑合实心菱形画成空心语义就变了。3.3 包图分层与依赖方向课程设计不必上微服务四层包图足够交代清楚也足够让老师看到你懂依赖倒置startuml package presentation (ui) as UI package application (service) as APP package domain (modelrepository接口) as DOM package infrastructure (mybatis/支付适配) as INF UI .. APP : DTO APP .. DOM : 调用领域对象 INF .. DOM : 实现 Repository 接口 APP .. INF : 仅通过接口引用 enduml规则只有一条依赖必须单向箭头一律指向被依赖方。domain 包里放实体和仓储接口infrastructure 包写 MyBatis Mapper 和支付网关适配器去实现这些接口application 只依赖接口。这样换掉支付渠道时改动只落在 infrastructure领域模型和用例规约都不用动。包内再按业务分包比如domain.order、domain.menu、domain.payment比按类型分包所有实体塞一个包好找太多。3.4 类图生成 Java 骨架与状态守卫类图上的方法签名可以直接转成代码重点是别把类写成只有 getter/setter 的数据袋行为能力要落在类里// domain/order/Order.java public class Order { private final String orderNo; private final ListOrderItem items new ArrayList(); // 组合关系 private OrderStatus status OrderStatus.CREATED; private Table table; // 关联可为空外卖单 public Order(String orderNo, Table table) { this.orderNo orderNo; this.table table; } // 状态守卫只有 CREATED 状态才允许改菜 public void addItem(Dish dish, int qty) { if (status ! OrderStatus.CREATED) { throw new IllegalStateException(订单已提交不能改菜: status); } if (!dish.isAvailable()) { throw new IllegalArgumentException(菜品已沽清: dish.getName()); } items.add(new OrderItem(dish.getDishId(), qty, dish.getPrice())); } // 金额计算内聚在订单里避免散落到 service public BigDecimal total() { return items.stream() .map(OrderItem::subtotal) .reduce(BigDecimal.ZERO, BigDecimal::add); } }status字段和addItem里的判断直接对应后面状态图的守卫条件两处必须一致。金额用BigDecimal而不是doublesubtotal()留在OrderItem里是因为单价乘以数量这件事只跟明细自己有关。类图到代码这一跳做扎实后面时序图上的每个-才有真实方法可以对应。4. 时序图、状态图与活动图把下单-支付-出餐的流程钉死三张动态图分工明确时序图看调用顺序和失败分支状态图看对象生命周期活动图看并发和流程分支。缺任何一张答辩时都会被追问「这一步失败了怎么办」。4.1 时序图一次下单到出餐的完整调用链startuml autonumber actor 顾客 participant 服务员端 as W participant OrderService as OS participant InventoryService as INV participant PaymentGateway as PAY participant KitchenDisplay as KDS 顾客 - W : 选菜下单 W - OS : submit(OrderDTO) OS - INV : tryLock(items) alt 库存不足 INV -- OS : InsufficientStock OS -- W : 提示缺货(具体菜品) else 库存充足 INV -- OS : Locked OS - OS : order.status SUBMITTED OS - PAY : createPayment(orderNo, amount) PAY -- OS : 支付结果 alt 支付成功 OS - KDS : dispatch(KitchenTicket) OS -- W : 下单成功 else 支付失败或超时 OS - INV : release(items) OS - OS : order.status CANCELLED OS -- W : 下单失败 end end endumlautonumber给消息自动编号写文档时可以直接引用「第 7 步失败」「第 5 步失败」。两个alt是嵌套的备选分支对应规约里两条扩展流。关键在于失败分支必须有补偿动作库存锁定失败什么都不用做支付失败则一定要释放预占库存否则菜品会凭空消失第二天盘点对不上。时序图上写明release(items)这一步就是提醒实现时别漏。4.2 订单状态机状态迁移表与守卫条件状态图最容易出问题的地方是状态爆炸把「支付中」「已通知厨房」「已打印小票」都当成状态图会失控。原则是状态只描述业务上可观察的阶段其余用事件或标志位表达。当前状态事件目标状态守卫条件动作CREATED提交订单SUBMITTED明细非空锁库存、生成出餐单CREATED顾客撤单CANCELLED—释放桌台SUBMITTED支付成功PAID金额一致推送厨房SUBMITTED支付超时CANCELLED超时 ≥ 5 分钟释放库存与桌台PAID厨房接单PREPARING菜品未沽清扣减库存PREPARING出餐SERVED全部菜品完成更新出餐单SERVED结账CLOSED已支付释放桌台PAID申请退单REFUNDING未出餐生成退款单REFUNDING退款成功CANCELLED—释放库存对应的状态图脚本startuml [*] -- CREATED CREATED -- SUBMITTED : 提交订单[明细非空] CREATED -- CANCELLED : 撤单 SUBMITTED -- PAID : 支付成功[金额一致] SUBMITTED -- CANCELLED : 支付超时[超时5min] PAID -- PREPARING : 厨房接单 PREPARING -- SERVED : 出餐[全部完成] SERVED -- CLOSED : 结账[已支付] PAID -- REFUNDING : 申请退单[未出餐] REFUNDING -- CANCELLED : 退款成功 CLOSED -- [*] CANCELLED -- [*] enduml方括号里是守卫条件必须能从订单自身字段算出来不能依赖外部查询结果否则状态机就没法做单元测试。CLOSED和CANCELLED都指向终止节点意味着这两个状态之后不允许再有任何改单操作代码里对应的就是addItem的状态守卫。4.3 活动图并台与厨房并发分单活动图的价值在并发。厨房有多台灶、多个档口一张订单要拆到不同档口并行制作全部完成后才能整体出品startuml start :接收出餐单; fork :凉菜档口制作; fork again :热菜档口制作; fork again :酒水档口取货; end fork :汇总出餐; if (全部菜品完成?) then (是) :通知服务员取餐; :订单状态 SERVED; else (否, 有沽清) :通知服务员与顾客; :退菜并重算金额; endif stop endumlfork/fork again/end fork表示并行分支end fork之后的节点要等所有分支完成才继续。这也是为什么出餐状态不能放在单个KitchenTicket上而要用「所有子单完成」来推导订单主状态。4.4 类图到 MySQL 表结构组合关系怎么建外键组合关系映射成子表加外键加级联删除关联关系只加外键不级联-- 订单主表会经常按桌台 状态筛选 CREATE TABLE t_order ( order_no VARCHAR(32) NOT NULL COMMENT 订单号, table_id BIGINT NOT NULL COMMENT 桌台ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 0创建 1已提交 2已支付 3制作中 4已出餐 5已结账 6已取消, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_no), KEY idx_table_status (table_id, status), KEY idx_created_at (created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表; -- 订单明细组合关系随主单级联删除 CREATE TABLE t_order_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL, dish_id BIGINT NOT NULL, qty INT NOT NULL, unit_price DECIMAL(10,2) NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_no) REFERENCES t_order (order_no) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细;status用TINYINT而不是字符串和状态图里的枚举一一对应注释里写清楚数字含义避免后期有人猜。version字段对应并台、加菜这类并发场景配合UPDATE ... WHERE version ?做乐观锁防止两个服务员同时改同一桌。idx_table_status是查「某桌当前有效订单」的常用索引没有它桌台状态校验会全表扫描。5. 餐厅系统 UML 交付与校验从 .puml 到架构设计文档图能不能长期用下去取决于两件事出图流程是否可重复分层约束是否能被自动检查。手工拖出来的图改三次需求之后就没人愿意更新了。5.1 批量出图并嵌进文档把所有.puml按目录放好一条命令全部导出成 SVG 再贴进 Word 或 Markdown# docs/uml 目录下按 01-usecase.puml、02-class.puml 命名导出到 build/uml java -jar tools/plantuml.jar -tsvg -charset UTF-8 -o ../build/uml docs/uml/*.puml-tsvg出矢量图放大不糊适合插进文档-charset UTF-8保证中文不乱码机器上默认编码不是 UTF-8 时这条必须加-o指定输出目录不写就和源文件混在一起。图名用两位数字前缀排序文档里的图号顺序就和脚本顺序一致需求变更时重跑一遍命令图全部刷新。5.2 用依赖测试守住包图包图画完就丢代码里照样互相乱依赖这种情况太常见。用 ArchUnit 把包图翻译成测试用例编译期就能拦住越界// src/test/java/com/canteen/ArchitectureTest.java AnalyzeClasses(packages com.canteen) class ArchitectureTest { ArchTest static final ArchRule 分层依赖 layeredArchitecture() .consideringAllDependencies() .layer(UI).definedBy(..ui..) .layer(APP).definedBy(..application..) .layer(DOM).definedBy(..domain..) .layer(INF).definedBy(..infrastructure..) .whereLayer(UI).mayNotBeAccessedByAnyLayer() .whereLayer(APP).mayOnlyBeAccessedByLayers(UI) .whereLayer(DOM).mayOnlyBeAccessedByLayers(APP, INF); }definedBy里的包名通配符要和实际包结构严格对应包名改了测试就会失效。这条规则和 3.3 节的包图完全一致UI 不能被任何层访问APP 只允许 UI 调DOM 只允许 APP 和 INF 调。ArchUnit 各版本 API 写法略有差异以项目实际引用的版本为准跑不起来先看layeredArchitecture()的方法签名。加上这个测试包图才真正变成了约束而不是一张插图。5.3 用例—类—表追踪矩阵与三个高频返工点文档收尾时做一张追踪矩阵三分钟就能查出漏项用例领域类数据表接口方法测试点提交订单Order、OrderItemt_order、t_order_itemOrderService#submit明细为空、库存不足转台并台Table、Ordert_table、t_orderTableService#merge并发占用、目标桌非空闲结账收款Payment、Ordert_paymentPaymentService#settle金额不一致、重复支付出餐确认KitchenTickett_kitchen_ticketKitchenService#finish部分完成、沽清退菜矩阵里任意一行出现空格就说明某个用例缺类、缺表或缺接口。三个最常见的返工点一是用例漏了转台并台导致桌台状态只有两态后期加状态要改状态图、改表注释、改守卫条件二是包图出现循环依赖多半是把仓储接口写进了 infrastructure正确做法是接口留在 domain、实现放 infrastructure三是状态爆炸把界面态当成业务态解决办法是问一句「这个状态顾客能观察到吗」观察不到就降级成标志位或事件。本文还有配套的精品资源点击获取
返回列表