简介:系统分析与设计是软件工程的关键环节,统一建模语言(UML)是其中的标准工具。这份复习试题面向需要掌握UML建模的高校学生与自学者,集中巩固系统分析与设计中的核心考点,尤其适合期末复习、考研备考及面试前突击。压缩包仅含一个Word文档,大小132KB,却浓缩了一套完整试题及参考答案。已有405人学习。内容覆盖关联多重度、类关系与组合、顺序图与协作图的区别、高内聚度、用例图与序列图作用等高频知识点,并进一步扩展到概念类识别策略、GRASP模式、统一过程四阶段、设计模式概念、需求分类FURPS等内容。所选示例包括客户与订单、电话用户、销售场景等,结合选择题、简答题与画图题,每道题均给出清晰答案或解析思路,可帮助读者系统梳理UML系统分析与设计的知识框架,提升应试能力与建模设计水平。
1. UML复习试题不是让你背答案,是补建模直觉
UML系统分析与设计复习试题这类资源,说白了就是把你对UML的零散印象,压成一张能查缺补漏的滤网。它的正文看起来是选择题、简答题的杂拌,实际是一条从类图多重度、交互图,到领域模型、SSD图和GRASP模式的完整复习链。我在带模拟项目X和某图像处理Demo的评审时,发现多数翻车不是因为不会画图,而是说不清“一个订单对应几个客户”这种多重度问题。这套题适合正在备考系统分析与设计、或者工作中要把设计文档补规范的开发者,它不输出标准答案大全,而是帮你把图理清,把该拿的分拿住。
2. 把多重度彻底啃下来:从关联语义到组合聚合的正确姿势
2.1 多重度不是方法调用次数
题1问“UML中关联的多重度是指什么”,正确答案是“一个类的实例能够与另一个类的多个实例相关联”。很多同学会选“一个类有多个方法被另一个类调用”,是因为把多重度理解成了运行时的方法调用频率。多重度回答的是结构问题:站在类A的视角看一个A实例,能同时和多少个B实例建立关联;站在类B的视角看一个B实例,又能反向对应多少个A实例。它是类图上用0..1、1、*、1..*标注的静态约束,不是代码执行次数。
“客户-订单”那道题是必考原型:
业务陈述:一个客户提交0个或多个订单;一个订单由一个且仅由一个客户提交。
解题顺序是先把业务陈述拆成两个方向:
- 对应每个“订单”类的实例,存在1个“客户”类的实例。因为“一个订单由一个且仅由一个客户提交”,订单这一端的对面是1。
- 对应每个“客户”类的实例,存在0个或多个“订单”类的实例。因为一个客户可以一个订单都不提交,也可以提交很多个。
所以答案第一空是B(1个),第二空是D(0个或多个)。我在批改作业时发现,最典型的错误是第二空选了“1个或多个”,原因是脑子里默认客户一定下单,忽略了“0个或多个”里的0。业务陈述里只要出现“可以没有”,就一定带上0。
画图时的标注位置也要注意:多重度写在靠近被关联类的那一端。比如客户对订单是一对多,那0..*要标在订单那一侧,不是客户那一侧。这个细节考试不一定会画,但评审设计文档时一看错位就知道建模没过关。
提示:做这类题先圈出“对应每个”三个字,前置词是视角类,答案数字永远落在另一个类的关联端。
2.2 组合是生命周期绑定
题2给了个有点别扭的表述:“类A由类B的一个实类和类C的1个或多个实类构成”。这里的关键词是“由……构成”,看到这个词优先考虑组合关系。组合在UML里用实心菱形表示,菱形画在整体那一端,也就是A这一端。B端的多重度是1,C端的多重度是1..*。
组合(Composition)和聚合(Aggregation)的区别,是很多人的知识盲区。组合是强聚合,部分的生命周期跟随整体,整体销毁时部分也要销毁。比如“订单由订单行组成”,订单删除时订单行没有独立存在的意义。聚合是弱关联,整体和部分可以各自独立存活,比如“班级包含学生”,班级没了学生还是学生。
判断方法:
- 题干出现“由……构成”“由……组成”,或者整体必须负责创建和销毁部分,用组合。
- 题干只描述“拥有”“包含”,但没有生命周期绑定,用聚合。
实操画图时,组合线的整体端画实心菱形,聚合线画空心菱形。很多教材会把聚合和组合统称为聚集,题9里说“聚集是一种特殊类型的关联”,就是考这个。选择题遇到“聚集是特殊关联”直接判对,遇到“组合是特殊实现”直接判错。
2.3 依赖、关联、泛化、实现:四种关系的图形识别
UML的关系题,本质上考的是图形记忆。我把常用的画法和语义整理成一张表,考前一晚过一遍基本不会错:
| 关系 | 画法 | 语义 | 典型识别句 |
|---|---|---|---|
| 依赖 | 虚线箭头 | 一个类临时使用另一个类,比如方法参数、局部变量、返回值 | “用到”“调用” |
| 关联 | 实线 | 长期结构性关系,通常表现为属性级别的引用 | “拥有”“关联” |
| 泛化 | 实线空心三角 | 继承,子类继承父类 | “是一种”“继承自” |
| 实现 | 虚线空心三角 | 接口与实现类的关系 | “实现接口” |
| 聚集 | 实线带空心菱形 | 关联的特例,整体与部分 | “包含”“由……组成” |
做题时最容易混的是依赖和关联。依赖是“方法里临时用一下”,用完就丢;关联是“类里长期持有对方引用”,通常对应一个属性。比如类A的方法里new了一个类B的对象传给别处,这是依赖;类A声明了一个B b成员变量,并在多个方法里使用,这是关联。
泛化和实现也经常画错:泛化是实线三角,实现是虚线三角。三角指向父类或接口。如果题目里画的是虚线箭头加三角,那就填实现。另外,题41这类题还会把聚合和关联并列放在选项里,记住聚合是一种特殊的关联即可。
3. 交互图双雄:顺序图与协作图的区别、转换与高频考点
3.1 顺序图强调时间,协作图强调结构
交互图是UML动态视图里的常客,题4和题72几乎年年出现。顺序图(Sequence Diagram)和协作图(Collaboration Diagram)都展示对象间的交互,但侧重点完全不同:
- 顺序图按时间顺序布图,清晰地展示消息之间的先后关系。缺点是水平方向空间占用大,对象一多,图的宽度就很难控制。
- 协作图按空间组织布图,对象间的连接关系直接画在图上,增加对象时比较方便,分支也少。缺点是消息顺序必须靠编号表达,消息多了读起来很费劲。
考试作答的口径一般是:顺序图强调的是交互的时间顺序,协作图强调的是交互的语境和参与交互对象的整体组织。如果想拿满分,再补一句“顺序图按时间顺序布图,协作图按空间组织布图;顺序图常用于展示方案,协作图常用于详细设计”。
题7和题16考得更细:在UML提供的图中,类图给出系统的静态设计视图;用例图对系统的行为进行组织和建模非常重要;序列图和协作图都是交互图,序列图描述以时间顺序组织的对象交互,协作图强调收发消息的对象组织结构。这类填空就是把图名和它的功能描述对上。
一个快速记忆法:看到“时间顺序”选顺序图,看到“对象组织结构”选协作图。考试时这两个词几乎就是选项的特征词。
3.2 顺序图消息序列的可抄写法
顺序图不是随便画几条箭头。以题21的销售场景为例,系统事件有四条:makeNewSale()、enterItem(itemID, quantity)、endSale()、makePayment(amount)。画顺序图的步骤是先列生命线,再按从上到下的时间轴画消息,最后补返回消息。
实际画图时,每条生命线顶端是对象名加冒号加类名,下面一条垂直虚线是生命线,对象在活动期间有一个窄长矩形叫激活条。消息箭头从发送方指向接收方,水平位置从上往下代表发生顺序。返回消息用虚线箭头,从右往左画。
用文本表示消息序列,可以写成这样:
收款员 -> 系统 : makeNewSale() 收款员 -> 系统 : enterItem(itemID, quantity) 系统 -> 系统 : 计算行小计和总金额 收款员 -> 系统 : endSale() 系统 -> 收款员 : 返回总金额 收款员 -> 系统 : makePayment(amount) 系统 -> 收款员 : 返回找零参数说明:
enterItem两个参数,itemID是商品标识,quantity是数量。这两个参数必须和类图里方法的签名一致。endSale没有参数,但系统会计算并显示总金额,所以这里有一条由系统返回给收款员的消息。makePayment接收amount,返回找零。画的时候不要漏掉返回消息,很多同学箭头只画一半。
协作图也画同样的消息,但表达方式不同:协作图上对象之间用连接线表示关联,消息用带编号的小箭头标在连接线旁边,顺序靠编号不靠位置。考场上有时候会直接让你画协作图,画的时候记得给每条消息编上序号。
3.3 协作图消息编号怎么展开成顺序图
协作图转顺序图是高阶考点。协作图的消息编号是嵌套式的,比如1、1.1、1.1.1、2,表示调用关系。展开成顺序图时,嵌套编号展开成上下排列的消息:
1: makeNewSale() 2: enterItem(itemID, quantity) 2.1: SalesLineItem::create() 3: endSale() 3.1: 计算总金额 4: makePayment(amount)展开时最容易翻车的一点是嵌套返回。2.1执行完后要回到2的调用处,再继续后续逻辑;如果直接把2.1当成消息链的第二个层次,展开后的时间轴就全乱了。我的习惯是先按编号编成树状结构,再从树根开始按深度优先展开,顺序图的纵向顺序就出来了。
4. 领域模型与SSD:从概念类识别到黑盒系统事件的推进
4.1 领域模型的本质是“真实世界的概念类”
题15说领域模型是一组表示真实世界概念类的模型,题50给了同样的选项,答案是概念模型。领域模型不是软件模型,里面不该出现按钮、数据库表、消息队列这些软件成分,它表达的是业务世界里的对象和关系。
识别概念类有两个策略,题18直接考这两个策略的名字:概念类类别表、标识名词短语。
概念类类别表的做法是按类别列举候选概念。比如业务对象(顾客、收款员)、交易(销售、支付)、产品(商品、商品说明)、位置(POS收款台)、单据(订单、发票)、组织(门店、仓库)。把业务陈述里的名词按这些类别归档,留下来的就是候选概念类。
标识名词短语的做法更直接:把题干里的名词和名词短语圈出来,然后逐个判断。题20里,“顾客带着购买的商品或服务来到POS收款台,收款员启动一次销售,收款员输入商品标识,系统记录商品,并且显示该商品说明、价格,并计算总金额”,可用的概念类答案是:顾客、POS收款台、收款员、销售、商品标识、商品、商品说明。
注意过滤规则:事件本身不是概念类(“计算总金额”是行为不是类);外部系统如果不是重点关注,也可以先放一边;“商品说明”这类信息是否独立成类取决于业务复杂度,如果说明有多语言版本,独立成类更合理。
4.2 可见性就是“能不能引用”
题14问什么是对象间的可见性。答案是一句话:一个对象能够“看到”或者引用另一个对象的能力。发送消息的前提是可见,如果你在顺序图里让A给B发消息,A就必须能拿到B的引用。
可见性不等于public/private。public/private控制的是类成员的对外暴露,而可见性控制的是对象之间能否拿到引用。常见四种可见性:
- 属性可见性:B是A的一个属性,A内任何方法都可访问B。
- 参数可见性:B作为某方法参数传入,只在方法体内可见。
- 局部可见性:在方法内部
new出来的对象,只在方法内部可见。 - 全局可见性:B可以通过全局变量或单例获取,任何地方都可见。
做题时判断依据是:消息发送者能不能在属性、参数、局部变量、全局范围里找到接收者的引用。设计时尽量优先用属性可见性和参数可见性,全局可见性会让类之间的依赖变得难以控制。
4.3 SSD图不允许打开系统黑盒
SSD图(系统顺序图)和顺序图长得像,但定位完全不同。题52的答案说明得很清楚:画SSD图时,不对系统的内部结构与功能进行描述。它把系统当成一个黑盒,只画外部参与者向系统发起的系统事件,以及系统的返回结果。
画SSD的步骤:
- 左侧画参与者,右侧画一个标着“系统”的对象框。
- 从参与者到系统画实线箭头,箭头上写系统事件名和参数。
- 从系统返回参与者的信息用虚线箭头表示。
比如销售用例,SSD就是参加者“收款员”向系统发四条消息,系统在endSale后返回总金额,在makePayment后返回找零。里面发生什么,SSD不画,那是顺序图的事。
SSD的价值在于把“外部可见行为”与“内部实现”解耦,先确定系统边界上发生的事件,再写操作契约,最后才设计内部对象。用例的概念也和这里衔接:用例(use case,有的教材叫用况)是描述参与者使用系统完成某个业务过程时的事件发生顺序;场景是用例的具体执行路径;参与者是外部角色。题48就是考这三个名词的定义,照着这层关系答就行。题73还考了用例模型是需求工作的重要制品,所以画用例图时,参与者、用例、关系三个元素一个都不能少。
5. 失分点排查:UP、GRASP与设计模式里那些容易翻车的地方
5.1 UP阶段的时间点总有人记混
统一过程(UP)的四个阶段是:初始、细化、构造、提交。题60和题76都在考这个顺序,前两个字就能拿分:初始阶段,细化阶段,构造阶段,提交阶段。
各阶段的主要工作:
- 初始阶段:编制简要愿景文档、业务案例、确定范围、粗略评估成本。
- 细化阶段:细化愿景文档、迭代实现核心构架、解决高风险问题、定义大多数的需求和范围、进一步评估成本。
- 构造阶段:迭代地实现系统的其余部分、准备部署。
- 提交阶段:beta测试、部署。
题41考了“定义大多数需求和范围”在哪个阶段完成,答案是细化阶段。很多人会被“初始阶段做需求”的先入为主带偏,但UP是迭代驱动,初始阶段只做粗略需求评估,真正的需求大头在细化阶段和后续迭代里才逐步定死。同样的坑出现在题43,问哪项不在初始阶段完成,答案是“定义大多数的需求”。
统一过程的核心思想也常出简答题:短时间盒的迭代式开发,开发过程中不断调整,早期解决高风险高价值的问题,不断与用户衔接获取反馈,早期构造核心体系结构,早期进入实现和测试,用用例驱动,用UML可视化建模,仔细管理需求控制变更。题62只要把这些点列出来,基本不会失分。
UP的工作流在题61里出现:业务建模、需求分析、设计、实现、测试。这五个词按顺序背即可。
5.2 GRASP和GoF:别把两套模式混在一起
题52问“下面哪一个模式不属于GRASP模式”,正确答案是GoF。GRASP是分配职责的通用原则模式,包括五个:信息专家、创建者、低耦合、高内聚、控制器。
每个模式解决的问题,整理成表更清楚:
| 模式 | 解决的问题 |
|---|---|
| 信息专家 | 分配职责给对象的基本原则是什么 |
| 创建者 | 谁负责创建某个类的新实例 |
| 低耦合 | 如何支持低依赖关系并增加重用 |
| 高内聚 | 如何把复杂性保持在可控制范围 |
| 控制器 | 谁来负责处理一个输入系统事件 |
高内聚这个概念的原文在题6和题83里出现两次,答题口径是:高内聚度是对一个类中各个职责之间相关程度和集中程度的度量。两个意思要写全:一是不要给一个类分派太多职责,在履行职责时尽量把部分职责分派给有能力完成的其它类;二是不相关的职责不要分派给同一个类。
GoF设计模式是另一套体系,常见的有单例、策略、组合、观察者、适配器。考试喜欢考“问题-解决方案”对应关系,比如:
- 单例:如何让一个类严格只有一个实例?定义静态方法返回单例。
- 策略:如何适应算法或规则的频繁变更?把每个算法封装成具有公共接口的类。
- 组合:如何按处理原子对象的方式处理组合对象?组合对象和原子对象实现相同接口。
- 观察者:如何维护对象之间的一致性?状态变化时通知所有依赖对象自动更新。
- 适配器:如何解决不兼容接口?加一个中间适配器对象转接。
题35里有个资料笔误:把观察者模式标成了Composite,这是错误的。观察者是Observer,组合才是Composite。背模式名不如背问题描述,判断题看到“维护对象一致性”就应该锁定观察者,而不是看边上的英文名。
5.3 高频踩坑记录
现象1:多重度选反。
原因:做题时只盯数字,不确认数字落在哪个类的关联端。比如“每个订单对应1个客户”和“每个客户对应多个订单”,数字相同但方向不同,不看题干前置词就很容易填反。
解决:先圈“对应每个”三个字,前置词是视角类,答案数字永远落在另一个类的关联端。画图时多重度标注在靠近被关联类的那一端。
现象2:顺序图与协作图的功能描述张冠李戴。
原因:只记得两个图名,不记得各自的侧重点。问“哪个强调组织结构”时,凭感觉选了顺序图。
解决:时间顺序→顺序图,组织结构→协作图。协作图的消息顺序靠编号,顺序图的消息顺序靠垂直坐标,这个差异是判断依据。
现象3:观察者模式被错误资料带偏。
原因:原题里观察者模式被署名成Composite,做题的人照着错误文本背,遇到“维护一致性”就选了组合。
解决:观察者模式的关键词是“状态变化、通知、自动更新”;组合模式的关键词是“原子对象、组合对象、相同接口”。两套关键词完全不同,按问题描述选而不是按名字选。
现象4:SSD图里画了一堆内部对象。
原因:把SSD当成普通顺序图,把系统内部对象和消息全画进去,丢了黑盒属性。
解决:SSD只保留参与者、系统对象、外部事件消息和返回值。所有内部对象交互挪到顺序图里画。
现象5:“定义大多数需求”选在初始阶段。
原因:把UP当成瀑布式流程,认为需求一定在前期全部定完。
解决:UP是迭代驱动的,大多数需求在细化阶段才定性,初始阶段只做范围确定和粗略成本评估。
6. 把交互图变成代码:一晚上能跑通的练习流程
题31和题72指向同一件事:读懂交互图,再结合其他条件把设计转成代码。这个能力考试和评审都用得上。我的练习流程分四步,一个晚上就能跑通。
第一步,把SSD或顺序图的消息列成清单。以销售用例为例,消息就是makeNewSale、enterItem、endSale、makePayment四条。第二步,横向匹配类图,消息的接收者是哪个类,对应哪个方法签名。第三步,把消息顺序映射为方法调用顺序,同时问自己每个方法需要哪些参数。第四步,按需补齐类的属性和关联,把能跑通的骨架代码写出来。
下面是一个简化实现,消息名和方法名一一对应:
class ProductSpec: def __init__(self, item_id, desc, price): self.item_id = item_id self.desc = desc self.price = price # 商品单价 class SalesLineItem: def __init__(self, spec, quantity): self.spec = spec self.quantity = quantity def line_total(self): # 行小计 = 单价 * 数量 return self.spec.price * self.quantity class Sale: def __init__(self): self.line_items = [] self.total = 0.0 def make_new_sale(self): # 对应 makeNewSale:启动一次新销售 self.line_items.clear() self.total = 0.0 def enter_item(self, spec, quantity): # 对应 enterItem(itemID, quantity),这里直接传入商品规格对象 line_item = SalesLineItem(spec, quantity) self.line_items.append(line_item) def end_sale(self): # 对应 endSale:计算并返回总金额 self.total = sum(item.line_total() for item in self.line_items) return self.total def make_payment(self, amount): # 对应 makePayment(amount):处理支付并返回找零 change = amount - self.total return change代码里的每个方法都能从顺序图里找到对应消息:end_sale对应消息endSale,方法内部计算总和然后返回;make_payment接收金额参数,返回找零;SalesLineItem在类图里是销售行的概念类。转码的核心是让消息名和类图里已有的方法签名保持一致,不要自己发明新名字。
从那以后,我每次评审设计文档,都强制自己先画一遍SSD和顺序图,再对着消息清单检查代码里有没有漏掉的方法。这个习惯帮我少踩了很多坑,因为图如果画对了,代码结构往往不会偏。你也可以拿这套复习题的销售场景练一遍,画图、列消息、写类骨架,三步走完才算真的掌握。希望帮到你。
本文还有配套的精品资源,点击获取