
简介UML系统分析与设计复习试题是面向软件工程、计算机相关专业学生及自学者的备考资料覆盖关联多重度、类图关系、交互图对比、高内聚度、四种UML关系等核心考点可用于课程期末复习、考研复试或软考准备。压缩包共1个doc文档大小132KB内容精炼、便于离线浏览与打印。目前已有405人学习使用具有较好的参考价值。文档以选择题、简答题和设计题形式组织涉及客户与订单的数量约束、顺序图与协作图的优缺点、概念类联系绘制、用例图设计、对象间可见性、领域模型等具体问题并附参考答案与解析其中还对顺序图与协作图、类图与用例图等易混淆知识点作了辨析并涵盖GRASP模式、FURPS需求分类等内容能帮助读者系统梳理知识框架、熟悉题型与解题思路提升UML建模与应试能力。1. UML 复习题里最容易被当成“常识题”的是关联多重度UML 复习题里最容易被当成“常识题”的是关联多重度问“一个类的实例能够与另一个类的多个实例相关联”答案给的是 B。但真正把它放进客户和订单的例子里能一次把方向、数字、生命周期全部答对的人并不多。多重度不是“关系的数量描述”而是 UML 类图上最底层的引用约束机制决定了对象之间允许有多少条连接也决定了组合、聚合、依赖在建模和代码层面的不同形态。搞懂多重度才能把类图从“画方块连线”提升到“表达可验证的领域约束”。2. 多重度类图上的数字不是装饰是对象的引用配额2.1 从“一对多”到“0..*”多重度到底约束了什么先明确一个概念多重度不是方法调用次数也不是属性共享数量。UML 里关联两端各自标注的多重度表达的是“这一端的一个实例可以对应另一端多少个实例”。试题里那道客户-订单题考得最典型业务陈述是“一个客户提交 0 个或多个订单一个订单由一个且仅由一个客户提交”。于是对应每个“订单”实例存在 1 个“客户”实例对应每个“客户”实例存在 0 个或多个“订单”实例。第一空选 B第二空选 D。这里的模糊点恰恰是“对应”这个词。你以为是左右呼应实际上每个空都要站在一个类的实例出发向另一个类看去。订单这边往客户看是 1客户那边往订单看是 0..。如果把方向反过来读订单端写 1..客户端写 0..*整个模型表达的边界条件就变了它允许一个订单属于多个客户这在零售场景里是灾难。多重度的设计动机不是让图变得“规范好看”而是给领域规则一个可检查的量化边界。数据库设计里它对应外键本身的非空约束与子表数量的上限对象设计里它决定了一个字段是单个引用还是集合。试题反复考这一点说明出题者想确认的并不只是记忆而是你能不能从自然语言描述中抽取出实例层面的数量关系。2.2 组合与聚合生命周期绑定的两种强度第 2 题里类 A 由类 B 的一个实例和类 C 的 1 个或多个实例构成要求画类图。这里的关键词是“构成”。这个题考查的是组合关系整体 A 拥有部分 B 和 C部分不能脱离整体单独存在。画法是 A 端用实心菱形B 端标 1C 端标 1..*。组合与聚合是 UML 里最容易被画错的一对聚合是空心菱形语义是“整体与部分的松散拥有”例如公司聚合员工员工离职后还能存在组合是实心菱形语义是“整体强拥有部分”部分的生命周期被整体托管例如订单项离开订单就没有独立的业务意义。判断依据就一条整体消亡时部分是否跟着消亡、是否没有其他整体同时拥有它。是组合不是聚合。从实现上看组合往往表现为整体在构造时创建部分而不是从外部注入部分。很多人在代码评审里纠结“这个类里 new 出来的对象算不算组合”答案是把生命周期问题转成所有权问题谁负责创建谁负责销毁谁对外不可见谁拥有不可替代性。多个对象共享同一个“部分”引用时组合语义就很难成立。2.3 从类图到实现多重度如何映射成字段与约束// 客户-订单客户端多重度 0..*订单端多重度 1 public class Customer { private ListOrder orders new ArrayList(); // 对应 0..* } public class Order { private Customer customer; // 对应 1不允许为 null public Order(Customer customer) { this.customer customer; } }这段代码把多重度翻译成了字段类型多值端用集合承载单值端用对象引用承载。注意构造函数强制要求传入 customer这就是在实现层面保证订单端至少有一个客户相当于把模型上的“1”变成了不变量。如果没有这个强制UML 图上标着 1代码里却允许摘除引用模型与实现就脱节了。数据库里对应关系更直观用 SQL 更能说明“1”和“0..*”的落地方式CREATE TABLE customer ( id BIGINT PRIMARY KEY ); CREATE TABLE orders ( id BIGINT PRIMARY KEY, customer_id BIGINT NOT NULL, CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customer(id) );orders.customer_id 上的 NOT NULL 约束就是 UML 订单端“1”的数据库形态customer_id 外键本身是订单对客户的引用。数据库不存储客户到订单的集合查询时通过外键反查这就是 0..* 在关系模型里的自然形态。理解这一层之后你再回看类图上的数字就能明白它是在约束“合法实例集合”的形状而不是装饰。3. 顺序图和协作图同一段交互的两种投影3.1 时间顺序 vs 空间组织交互的两种坐标第 4 题和第 72 题反复在问顺序图与协作图的区别。核心答案是一句话它们描述的是同一组对象交互但投影不同。顺序图把时间放在纵轴对象生命线水平排开消息按发生先后从上往下画像示波器里展开的事件序列协作图把对象和连接布局在二维平面上消息顺序退居为编号像电路图上标的信号时序号。这个差异不是风格选择而是表达能力的取舍。顺序图的水平方向被生命线占据对象一多图就横向膨胀但它的垂直时间轴让消息顺序一目了然任何读者都能直接读到“先做什么后做什么”。协作图把对象放在空间位置上新增对象时只需在空白处加一个节点再接一条线水平空间压力小但消息顺序全部压在编号上消息一多编号深度变得难以追踪。试题里那句“协作图在增加对象时比较容易而且分支也比较少但如果消息比较多时难以表示消息之间的顺序”说的正是这种取舍。我一般这么理解表达事件流、系统操作、调试场景优先顺序图表达对象协作的整体语境、辅助设计类分配职责、探讨对象间连接是否合理优先协作图。两种图可以互相转换UML 工具里一键切换也是常见功能但转换无损不代表两种视角等价——一个偏时域一个偏空域读图的心智负担不同。3.2 消息编号协作图如何把时间压缩进空间协作图能摆脱时间轴依赖靠的是消息编号协议。这个协议本质上是一个调用树编码顶层消息按 1、2、3 编号每个消息内部的嵌套调用用小数点扩展比如 1.1、1.2其中 1.1 是 1 的执行过程中触发的子消息。返回消息一般也编号但不参与顶层顺序的计数。以销售场景为例协作图上可以看见编号消息层级1makeNewSale()顶层1.1create()1 的子消息2enterItem(itemID, quantity)顶层2.1addLineItem(itemID, quantity)2 的子消息3endSale()顶层3.1getTotal()3 的子消息这个表格展示了协作图还原时序的方式第一列决定阶段顺序小数点深度决定嵌套层次。读图时先看顶层序号确认大流程分几步再看每个顶层序号下挂了多少个子消息子消息之间又按 1.1、1.2 的顺序排列。写图时最容易犯的错误是把嵌套消息平铺成独立编号比如把 1.1 写成 2这会彻底破坏时序含义——本来发生在 makeNewSale 内部的创建动作被误读成发生在 makeNewSale 之后。协作图重视空间布局、允许对象被多个交互复用这套机制适合早期设计阶段快速检查对象之间是否存在多余连接。它牺牲的直观顺序由编号协议补偿。所以复习时比“谁会画”更重要的是“谁读得懂编号背后的调用树”。3.3 SSD 与领域模型黑盒契约和概念类的分界系统顺序图SSD是试题里另一个高频考点第 49、51 题都在考它。SSD 与一般顺序图的区别非常明确SSD 把整个系统当成一个黑盒图上只有参与者、系统边界和系统事件不出现任何内部对象和内部消息。试题第 51 题的正确答案是 D“不对系统的内部结构与功能进行描述”这句话很多初学者觉得“怎么可能”但这正是 SSD 的设计意图。SSD 回答的问题是“外部参与者以什么顺序触发了系统的哪些操作”而不是“系统内部怎么响应”。它服务于系统操作契约先定义系统对每个事件的黑盒行为再在设计阶段把系统生命线展开成内部协作。以 POS 场景为例SSD 上只画收款员 - 系统: makeNewSale() 收款员 - 系统: enterItem(itemID, quantity) 收款员 - 系统: endSale() 收款员 - 系统: makePayment(amount)这四条消息就是系统的公共接口。接下来设计阶段的工作是把“系统”这条生命线拆成注册、收藏、销售等多个对象之间的协作。为什么要先黑盒再白盒因为需求阶段过早暴露内部结构会让利益相关方把注意力放在实现细节上而领域模型此时应该只反映真实世界的概念类比如顾客、收款员、销售、商品而不是软件部件。先定契约、后谈实现这条顺序在 UP 里贯穿始终。4. GRASP 与高内聚职责分配不是审美是复杂度控制4.1 信息专家为什么“谁持有数据谁干活”是默认答案GRASP 五模式里信息专家解决“分配职责给对象的基本原则是什么”答案是把职责分配给拥有完成该职责所必需的信息的对象。听上去像一句正确的废话但把它落到 POS 例子上就会发现这个原则直接决定了类之间会不会产生多余的绕路。假设“计算销售总额”这个职责候选对象有收款员、POS 系统、销售对象。谁拥有计算所需的信息销售对象拥有商品明细列表每行明细又有各自的小计。让销售对象自己计算总额职责执行者和数据源在同一个类里不需要向其他对象索要数据。写成代码public class Sale { private ListLineItem lineItems; public Money getTotal() { return lineItems.stream() .map(LineItem::getSubtotal) .reduce(Money.ZERO, Money::add); } }getTotal 读取的是自己类内部的 lineItems这就是信息专家的机制本质信息局部性。如果反过来让收款员或外部服务来算总额收款员就必须先向销售对象要 lineItems再逐条访问商品价格协作路径变长任何一端的数据形态变化都会牵动多个类。信息专家之所以是 GRASP 里的默认答案是因为它尽可能把职责压缩进最小的信息范围内从根源上降低耦合。这里要注意“信息”不一定是对象自己存的字段也可以是它能够通过已有关联间接获得的信息。如果对象能通过已有连接在几步之内拿到数据它仍然算信息专家。只有当数据源完全无关时才需要引入纯虚构类或中间者来解耦。4.2 创建者、控制器与低耦合把路由和出生分开创建者模式解决“谁负责创建某个类的新的实例”原则是如果某个对象聚集了需要传给新实例的数据或者与待创建对象有聚合关系就让那个对象负责创建。控制器模式解决“谁来负责处理一个输入系统事件”原则是把系统事件的接收职责交给一个代表系统或代表用况的控制器对象而不是放任每个外部事件直接操作内部任意对象。这两个模式很容易被误解为“让一个类包办一切”。初学者常常把所有事件都丢给控制器makeNewSale 让控制器建 SaleenterItem 让控制器去改 Sale 的商品明细endSale 也由控制器算总价。结果控制器涨成一个上帝类Sale 反而退化成数据结构。正确的分工是控制器只负责接收外部事件和路由业务职责按信息专家原则下放创建职责按创建者原则就近分配。路由、业务、创建是三种不同性质的职责混在一起就是违背高内聚。低耦合模式是对这种混用的制衡。它不要求类之间完全没有关联而是要求关联尽量少、尽量稳定。信息专家跑偏的典型症状就是“为了低耦合把所有计算搬到外部工具类”结果是耦合低了一个类却损失了信息局部性全局的修改点反而更多。低耦合和高内聚之间需要权衡不是越低越好。4.3 高内聚的度量什么叫“相关职责集中”试题第 6、83 题对高内聚的定义一个类中的各个职责之间相关程度和集中程度的度量。这个定义值得拆开看。“相关”是质量维度不是数量维度。两个职责如果能共享同一批字段、或在同一次事件里被顺序触发它们就是相关的一个管打印报表、一个管连接数据库的方法即使都写在同一个类里也不代表高内聚。我判断一个类是否高内聚的实操标准是你能不能用一个短句说清这个类负责什么。能说清类名就是那个短句的主语说不清甚至要用“以及”连缀两个主题这个类就大概率需要拆分。高内聚还包含另一层意思不要把一个类的职责分派到能胜任的其它类之外。试题里那句“不要给一个类分派太多的职责在履行职责时尽量将部分职责分派给有能力完成的其它类去完成”可以翻译成职责转移的门槛不是“别的类会做就分出去”而是“别的类拥有更相关的信息才分出去”。策略模式在这里是个典型的正面案例。POS 系统里按计价规则计算单价规则会频繁变更解决方式是定义一组算法接口每个规则封装成一个策略类让销售对象在运行时选择具体策略public interface PricingStrategy { Money calculate(LineItem item); } public class QuantityDiscountStrategy implements PricingStrategy { Override public Money calculate(LineItem item) { // 数量折扣规则 return item.getSubtotal().applyDiscount(0.9); } }策略类各自内聚一种规则销售类只依赖 PricingStrategy 接口规则变更不再波及核心流程。这正是高内聚与低耦合协同作用的地方策略模式没有消除算法变化但把变化隔离在了稳定的接口之后。5. UML 复习常见问题排查五个翻车现场和它们的原理5.1 多重度方向读反一个订单为什么不能属于多个客户现象画客户-订单类图两端都标 0..*理由是“一个客户有很多订单一个订单也可以有很多客户”。原因没有站在实例方向逐端分析而是把业务对象的多重度当成了数据库表之间的多对多关系。业务陈述里“一个订单由一个且仅由一个客户提交”已经把订单端锁定为 1这个约束来自领域规则不是建模者自由选择的。解决标注前强制写两句话“从客户的一个实例出发看它对应几个订单实例从订单的一个实例出发看它对应几个客户实例”再分别落两端。顺序图场景检验同样有效如果允许一个订单属于多个客户那么订单的归属方不明账务和配送都无法收敛这从常识上就能排除 0..。试题的标准答案是客户端 0..、订单端 1方向一错整套图的意义就变了。5.2 组合聚合不分空心菱形实心菱形差的是一个生命周期现象“公司由部门构成”和“订单由订单项构成”都画成空心菱形。原因只看到“由...构成”就画组合没判断整体消亡时部分是否随之消亡。部门在公司注销后作为行政概念还会存在员工可以流转所以是聚合订单项被订单整体拥有订单作废时订单项没有任何独立业务意义是组合。解决用生命周期问题测试整体销毁后部分还有没有独立的业务价值没有实心组合有空心聚合。落到代码上也可以辅助判断组合里整体负责创建部分部分不对外暴露可复用的引用聚合里部分由外部注入整体只负责组装。试题里“类 A 由类 B 的一个实例和类 C 的 1 个或多个实例构成”应画实心菱形B 多重度 1C 多重度 1..*。提示实心菱形标在整体端即拥有部分的那一端。5.3 协作图消息编号平铺时间顺序要靠调用树还原现象协作图上消息标成 1、2、3、4、5 五个平铺序号看起来像把顺序图压扁了。原因把协作图理解成“没有时间轴的顺序图”忽略嵌套调用的层级。平铺编号无法表达一次消息内部又触发哪些子消息读图的人只能知道消息先后不知道谁调用了谁。解决画之前先画调用树。makeNewSale 是顶层 1它内部创建 Sale 是 1.1enterItem 是顶层 2它内部创建 LineItem 是 2.1每个顶层消息重新从整数开始嵌套用小数点。检查技巧是从 1 跳到 2 之前1 下面挂的所有子消息必须全部结束。反过来读到 1.3 时如果没有 1.1、1.2 的铺垫要么编号缺层要么出现了跨层跳号这种图信息是丢失的。5.4 用例图画错参与者把系统内部模块当人用现象在用例图里画“数据库”“登录验证模块”作为参与者跟“电话用户”并列。原因混淆了外部角色和系统内部部件。参与者定义是“与系统交互的外部角色”它是用况的发起者或受益者数据库和验证模块是对系统责任的内部分担者不对用况负责。解决先画系统边界再列出边界外访问系统的角色。电话用户是参与者“使用电话卡”和“对方付款”是用例数据库不是参与者它只是用例实现阶段的构件。如果确实需要外部系统协作也把整个外部系统当一个参与者而不是拆出内部模块参与编排。5.5 SSD 里画内部对象黑盒契约画成白盒实现现象SSD 上出现“系统 - Sale: create()”“系统 - 收款台: display()”这类内部消息。原因把 SSD 和一般顺序图混用。SSD 是系统操作契约的黑盒视图用来记录参与者向系统发起的系统事件而不是记录系统内部如何分工。解决写 SSD 时立一个规矩系统只有一条生命线参与者只有外部角色消息只能是参与者发给系统的系统事件内部对象一个都不出现。画完做一次白盒检测如果发现任何两个内部对象发生消息就把它删掉挪到协作图或交互图阶段。这个克制不是画图技术问题而是分析和设计两个阶段的边界问题——需求阶段过早展开内部结构会迫使利益相关方讨论一种还没设计的实现。6. 设计取舍UP 的迭代节奏和用例驱动到底解决了什么6.1 四个阶段不是瀑布是风险驱动的检查点UP 四阶段——初始、细化、构造、提交——常被误解为瀑布的另一个名字。但从各阶段的任务分配看它实际是风险驱动的检查点初始阶段只回答“项目值不值得做”做愿景文档、粗略成本评估细化阶段集中解决高风险问题迭代地实现核心架构定义大多数需求和范围构造阶段迭代实现剩余功能提交阶段做 beta 测试和部署。需求定义放在细化阶段而非初始阶段正是承认需求会随迭代反馈逐步清晰而不是在源头一次定死。6.2 用例驱动、增量开发与变更控制用例驱动把需求组织成“参与者用系统完成业务过程”的事件顺序先从外部描述系统必须做什么再指导内部设计。这和 SSD 的定位一脉相承契约先行实现后置。增量开发进一步把复杂用况拆成多个迭代按优先级在不同迭代里实现反馈周期被压缩错误在早期暴露。迭代、用例、UML、变更控制这四件事在 UP 里互相咬合没有哪个是多余的。6.3 我的习惯我从一次把多重度方向读反的翻车里养成一个习惯做任何 UML 题先不看图也不看选项而是把题面里的业务陈述改写成“一个 X 对应多少个 Y”的句式逐条验证方向再落图。这套流程放在复习《UML系统分析与设计复习试题》这类题集时特别有用它让每道题都变成一次约束提取练习而不是答案记忆。从那以后我每次画类图都强制走一遍这个句式速度和准确率反而都上去了。希望帮到你。本文还有配套的精品资源点击获取