软件工程这门课里,类图绝对是个绕不过去的大块头。不管你是期末突击复习、正在做课程设计,还是准备毕业设计答辩,几乎都会被问到“你这个系统有哪些类、类之间是什么关系”。我当年复习软件工程的时候,最头疼的就是那一堆箭头:实线、虚线、空心三角、实心三角、空心菱形、实心菱形,排列组合起来快把人绕晕。后来我想明白了一个道理——类图本质上就是在回答三个问题:系统里有哪些东西、每个东西长什么样、东西和东西之间怎么协作。搞透这个逻辑,复习效率会高很多。这篇文章把我整理复习笔记时总结的方法、踩过的坑,以及StarUML、Visio、IDEA、Eclipse这些工具的实操体验都写出来,给正在被类图折磨的同学做个参考。
1. 复习类图之前,先把它在软件工程里的位置搞清楚
说实话,很多人复习类图是直接跳到“怎么画”这一步的,结果画出来总是差点意思。问题不在手,在于脑子里没有建立整体框架。类图不是孤立的知识点,它是软件工程分析设计阶段的核心产物,前面连着需求分析,后面接着编码实现。你先搞清楚它的定位,再看那些规则就顺了。
1.1 类图属于静态建模,不是动态建模
软件工程里建模分两大类:静态建模和动态建模。类图属于前者,它只关心系统“在结构上长什么样”,不关心“某个时刻发生了什么”。动态建模通常用时序图、协作图、状态图来描述,它们解决的是对象之间如何交互、状态如何流转的问题。类图解决的是“有哪些类、类的职责是什么、类与类之间有哪些静态关系”的问题。
搞清楚这点很重要,因为考试里经常出判断题或者选择题,问你类图是描述系统动态行为还是静态结构。记住:类图是静态结构图,描述的是系统的词汇表和结构约束。它里面的每个类、每条关系,在系统运行期间一直存在,不会因为某个操作触发而临时变化。
类图在软件开发流程中的位置也很清晰:需求分析阶段产出用例图,确定系统“要做什么”;到了设计阶段,就要把这些需求翻译成类和对象,画出类图,确定系统“怎么做”。也就是说,类图是从需求到代码之间的桥梁。你要是只考理论,记住这个流程就够了;要是做课程设计,类图就是设计文档里必须有的核心交付物,评审老师基本都会细看。
1.2 类图的三个组成要素:类、接口、关系
类图看起来元素很多,其实核心就三样:类、接口、关系。把这三样搞明白,类图就掌握了一大半。
类的画法是一个三栏矩形框:第一栏写类名,第二栏写属性,第三栏写方法。这是UML最基础的约定。类名通常用名词,首字母大写,比如Student、Book、Order。属性栏存放成员变量,格式是“可见性 名称:类型 = 默认值”,比如- title: String = "untitled"。方法栏存放操作,格式是“可见性 名称(参数列表):返回值类型”,比如+ borrowBook(book: Book): boolean。
可见性符号也是必考内容:加号+表示public,减号-表示private,井号#表示protected,波浪号~表示包内可见。复习的时候可以这么记:加号是公开场合,减号是私人日记,井号是只给子类看的家书,波浪号是小范围朋友圈。
接口在类图里通常用类框加<<interface>>构造型来表示,也可以简化成小圆圈(棒棒糖记号)。接口本身不实现方法,只定义方法签名,类是接口的具体实现。
关系是类图里最复杂也最容易丢分的部分,我单独在下一章展开。这里先记住一句话:类图中只有这六种关系——依赖、关联、聚合、组合、泛化(继承)、实现。没有第七种,考试时遇到陌生关系描述,基本都可以归到这六类里。
2. 类图关系是复习的重灾区:六种关系怎么区分
我在复习时最大的痛点就是六种关系的箭头符号。说实话,这些符号没有任何认知负担,就是死记硬背,但难点在于给一段需求描述,你要能判断出该用哪种关系。这一章我把六种关系从“语法特征”和“语义特征”两个角度拆开讲,语义特征才是考试真正考的东西。
2.1 依赖关系:最弱的关系,临时用一下
依赖关系画成带箭头的虚线,箭头指向被依赖的一方。语义上,它表示一个类“用到”另一个类,但这种用是临时的,不是长期持有的关系。
什么时候会用到依赖?最常见的三种场景是:类的某个方法把另一个类当参数传入、方法内部局部变量创建了另一个类的对象、方法的返回值是另一个类的对象。比如Teacher类有个方法teach(Course course),Teacher和Course之间就是依赖关系,因为Course只是方法签名里的一个参数,Teacher并没有把Course作为自己的成员变量长期保存。
考试辨析的重点是:依赖和关联怎么区分?最简单的方法就是看A类里B类出现的“位置”——如果B只出现在A的方法里面,是依赖;如果B成为A的属性(成员变量),那就是更高级的关系。依赖是一种“使用”关系,关联是“拥有”关系。生活里类比的话,你打车去机场,你和出租车司机之间就是依赖关系,用完就结束;而你的手机是你日常持有和使用的物品,就是关联关系。
2.2 关联关系:实线箭头,长期持有
关联关系表示一个类的对象“知道”另一个类的对象,并且这种知道是长期存在的,通常体现在类的属性字段上。画法是一条实线,方向上可以画单向箭头,也可以不画箭头表示双向关联,甚至可以画成“一根线挂两个类”的自关联。
关联关系里有一个高频考点:多重性(multiplicity)。它表示一个对象可以对应多少个另一个对象。常见符号包括:1(正好一个)、0..1(零个或一个)、0..*(零个或多个)、1..*(一个或多个)、*(多个)。比如学生和借书证是一对一关联,学生和选修课程是多对多关联。
自关联是个容易忽略的点,比如员工类Employee有一个属性“上级经理”也是Employee类型,这就是自关联。实线上从Employee出发指向Employee自己,标明角色名“经理”和“下属”就可以。很多同学画关联的时候只记得类和类之间的线,忘记角色名和多重性,这不完整。考试阅卷时,多重性和角色名往往就是给分点。
2.3 聚合与组合:空心菱形和实心菱形的区别
聚合和组合是关联关系的两种特例,都表示整体与部分的关系,画法都是实线加菱形,菱形在“整体”那一端。区别在于空心还是实心,以及整体和部分的生命周期是否绑定。
聚合是空心菱形,表示“整体拥有部分,但部分可以脱离整体独立存在”。典型的例子是班级和学生:班级没了,学生这个对象还可以存在,他可以去别的班级。再比如图书馆和书——图书馆解散了,书还是客观存在的。
组合是实心菱形,表示“整体与部分同生共死”,部分不能脱离整体独立存在。典型的例子是订单和订单项:订单一旦删除,订单项必须同时删除,因为订单项离开了订单就没有意义。再比如人和心脏:人不在了,心脏这个对象也就不存在了。组合关系还有一种表现:部分类创建的时候,通常是整体类在构造方法里直接new出来的,而不是从外部传进来的。
考试里最经典的辨析题就是“学生和班级是聚合还是组合”。这题没有唯一标准答案,关键看你描述的业务规则:如果班级解散后学生可以转到其他班级继续存在,是聚合;如果班级是学生不能或缺的归属,学生和班级的生命周期完全绑定,那就是组合。拿分技巧是:做题时先看题目里有没有关于生命周期的描述,比如“删除后”、 “创建时”、“同时”这类词。有“同生共死”语义就是组合,有“分开了仍然能活”就是聚合。
2.4 继承(泛化)与实现:两个空心三角的差别
继承和实现都画三角形,但一个是实线,一个是虚线。继承(泛化)是实线加空心三角形,三角形指向父类;实现是虚线加空心三角形,三角形指向接口。
语义上,继承发生在类和类之间,表示“is-a”的关系,子类继承父类的属性和方法,并可以扩展。比如Student类和Teacher类都继承自Person类。实现发生在类和接口之间,表示类实现接口中定义的方法契约。比如MySQLDatabase类实现了Database接口,接口定义了connect()、disconnect()方法,实现类必须给出具体代码。
用编程语言来对照理解会非常快:继承就是面向对象语言里的extends,实现就是implements。复习时如果忘了画法,就想到Java里“extends是实线、implements是虚线”这个记忆点,配合空心三角形,基本不会错。
2.5 一表速查:六种关系对照
这一张表是我复习后期贴在书桌上的速查表,每次做题前先扫一眼,准确率提升不少。
| 关系类型 | 画法 | 语义关键词 | 实例 |
|---|---|---|---|
| 依赖 | 虚线 + 普通箭头 | 临时使用、方法参数、返回值 | 教师依赖课程作参数 |
| 关联 | 实线 + 普通箭头(可无) | 长期持有、属性字段、多重性 | 学生长期持有学生证 |
| 聚合 | 实线 + 空心菱形 | 整体与部分、可分离、共享 | 班级与成员 |
| 组合 | 实线 + 实心菱形 | 整体与部分、同生共死 | 订单与订单项 |
| 继承/泛化 | 实线 + 空心三角形 | is-a、extends、父类子类 | 学生继承人员 |
| 实现 | 虚线 + 空心三角形 | implements、接口契约 | 数据库类实现数据库接口 |
3. 从一段文字需求到画出类图:直接可用的三步流程
复习时最怕的就是“给我一段需求,画出类图”这种大题。其实这类题有固定套路,掌握后就像套公式一样。我把这个流程总结成三步:圈名词找候选类、把属性方法填进类里、根据动词确定关系。
3.1 第一步:用名词划出候选类
拿到需求描述后,先通读一遍,把里面的名词全部圈出来。这些名词往往是候选类。比如题目给了一段“图书管理系统支持管理员录入图书,读者可以查询图书、借阅图书和归还图书”的描述,圈出来就是:管理员、图书、读者、借阅。
圈完之后不要急着画,先做筛选。有三类名词要剔除:
- 同一事物的不同叫法,只保留一个。比如“图书”和“书籍”是一个意思,合并成一个类。
- 纯粹是另一个类的属性。比如“书名”、“编号”、“作者”这些,通常只是Book类的属性,不是独立类。
- 太宽泛的系统名。比如“系统”、“子系统”、“管理”,这类一般不要直接作为类,除非你画的是系统边界类。
经过筛选后的名词,基本就是你的候选类清单。这个步骤不用太过纠结,类图的粒度由你的系统规模决定,课程设计画到十几个类很常见,考试题一般就五六个类。
3.2 第二步:把属性、方法填进类里
候选类确定后,再回看需求里的“动词+名词”结构。动词通常对应方法,动词关联的名词对应参数或返回值。比如“查询图书”对应searchBook(title: String): List<Book>,“借阅图书”对应borrowBook(book: Book): boolean,“归还图书”对应returnBook(book: Book): boolean。
属性则来自于描述类特征的名词。比如Book类的属性有bookId: String、title: String、author: String、isBorrowed: boolean。注意一个原则:属性是数据,方法是行为。属性描述对象“是什么”,方法描述对象“能干什么”。
可见性设置也有规律:属性一般设为private(外部不能直接改),类对外提供的方法设为public(供别的类调用),内部辅助方法可以设为private。这对应软件工程里“高内聚、低耦合”的设计思想,考试时标注清楚即可拿分。
3.3 第三步:根据动词和量词确定类之间的关系
关系判断是这类题目的大头。我的经验是:先把需求里的“描述词”翻译成关系类型,再画出对应符号。
如果需求里出现“拥有”“包含”“由…组成”这类词,判断是聚合还是组合,看生命周期。出现“继承自”“是一种”“实现接口”,对应泛化和实现。出现“使用”“调用”“作为参数”,对应依赖。如果两个类之间的“拥有”关系没有明显生命周期约束,画关联就够了,不要强行套聚合组合。
关联关系的多重性也要从需求里提炼。比如“一个读者可以借多本图书”,说明Reader和Book的关联在“借阅”这个语境下是一对多,对应多重性1和0..*。多重性写在关联线两端,贴近对应类的一端。如果需求里没有明确数量,就分析业务常识:一个订单包含1到多个订单项,一个订单项只能属于1个订单。
3.4 完整实例:图书馆借书系统
用一个常见题目完整走一遍流程。需求描述:
“图书馆可以购买新书并登记入库。读者凭借书证借阅图书,每本图书同一时间只能被一个读者借走,读者最多可以同时借5本图书。学生和教师都是读者,教师借书数量不限。每次借阅会产生一条借阅记录,记录借书时间、还书时间和经手管理员。”
第一步圈名词:图书馆、书、读者、学生、教师、借书证、借阅记录、管理员、时间。筛选后核心类有:Library、Book、Reader、BorrowRecord、Librarian、Student、Teacher。
第二步分类关系:Student和Teacher继承自Reader,是泛化关系,实线空心三角,从Student指向Reader。Library聚合Book,因为图书馆可以购买新书后书依然存在,是空心菱形。Reader和Book之间是借阅关联,因为“借阅”这个行为长期存在,而且有借书数量和时间的约束,用一条带多重性的关联线连接。
第三步细化多重性:一个Reader可以借0到5本书,对应0..5;一本Book同一时间只能被一个Reader借走,对应0..1。BorrowRecord和Book是1..1的关系,因为每条记录必须对应一本具体图书;BorrowRecord和Reader是0..*的关系,因为一个读者有多条记录。Librarian和BorrowRecord是1..0..*的关系,每条借阅记录由一位管理员经手。
最终画出来,这张类图就包含了泛化、聚合、关联三类关系,覆盖了考试大部分得分点。遇到类似题目,按这个流程操作,基本不会漏掉关键类和数据。
4. 工具实操:StarUML、Visio、IDEA、Eclipse四条路线
复习和课设阶段,画类图的工具选择很关键。工具选对了,能把精力集中在思考上;选错了,光调连接线就能耗掉一下午。我这几款都用过,分别说说适用场景和踩过的坑。
4.1 StarUML:课程设计和复习画图最稳的选择
StarUML是我最推荐给学生的工具,免费、上手快、专为UML设计,不用为“线连不上”发愁。
画图步骤很简单:新建项目后,在左侧的Model Explorer里右键,选“Add Diagram”再选“Class Diagram”。左侧工具箱里能看到Class、Interface、Association等元素。把类拖到画布上,双击类框,在右侧属性栏里添加属性和方法,还可以设置可见性、类型和默认值。关系连线在工具箱里选择对应的按钮(Dependency、Association、Aggregation、Composition、Generalization、Realization),从源类拖到目标类即可。
细节上注意三点:第一,添加属性方法时,Type栏一定要写类型,否则导出图里属性只有名字,阅卷老师会认为你没掌握格式;第二,画聚合和组合时,要保证菱形在多边形上“整体”那一端,拖拽方向别反了,一般是从整体拖到部分;第三,关系线上双击可以设置多重性(Multiplicity)和角色名,这是考试给分点,别漏掉。
StarUML的导出功能也好用,画完后选File -> Export Diagram,可以导出PNG和SVG,直接插到课程设计文档里清晰度很高。
4.2 Visio画类图的几个坑
用Visio画类图的人也不少,尤其是写课程设计文档时,很多人习惯用Visio。Visio功能强大,但它的UML模板和普通人直觉里的“画图”不太一样,容易踩坑。
Visio里新建时选“类别 -> 软件和数据库 -> UML类”,会打开一个专门画UML的模板。左侧形状里能找到“UML类”形状,包含类框、接口等。属性栏添加方法和属性时,右键类形状 -> 形状显示选项,可以勾选是否显示属性栏和方法栏。关系线用顶部菜单“连接线”里的关联、聚合、组合等专用线,不能用普通的“直线”、“箭头”连线,否则画出来没有菱形和三角符号,评审老师一眼就能看出不规范。
Visio最让我难受的是关系线的多重性标注。很多版本默认不显示,需要右键关系线 -> 设置多重性,手动输入。设置完还得调整文本位置,很容易叠在线的中间把线挡住。我的建议是:如果只是画个示意图,自由画可以;但如果这是要交的课设文档,还是优先StarUML、PlantUML这类专业UML工具,Visio留给画流程图。
4.3 IDEA和Eclipse自动生成类图:适合逆向理解现有代码
如果你不是从需求画类图,而是想从已有的Java代码快速得到一张类图,那用IDEA或Eclipse的效率远高于手画。
IDEA里,右键一个类文件,选择Diagrams -> Show Diagram Popup,立刻生成包含该类和它关联类的局部类图。如果想看整个包的类图,右键包名 -> Diagrams -> Show Diagram即可。生成后可以右键任意类,选择Add Simple Dependency或Add Class手动补充关系。类图支持导出为图片,导出前先清理掉那些自动识别出来的过多依赖,不然图会显得很乱。
Eclipse本身不带UML图功能,需要装ObjectAid UML Explorer插件。安装后,右键项目 -> New -> ObjectAid Class Diagram,会生成一个空的类图文件,然后把Java类直接拖进去,插件会自动解析类之间的关系并用线连接。还有一个老牌插件AmaterasUML,功能类似。
这些工具生成的类图适合“逆向复习”:比如你拿到一份源代码,先看类关系再读逻辑,能快速理解系统结构。但注意,自动生成的类图会把你代码里所有引用关系都画成依赖,导致图上到处是虚线,并不符合UML规范中对关系的语义划分。所以自动生成的图通常只用来辅助理解,直接交作业还得手动整理一遍。
5. 复习考试中的高频考点与易错点
最后这部分是我结合自己做题和批改作业经验整理的“送分点”和“丢分点”,尤其是那些一到考试就容易写错的地方,建议重点看几遍。
5.1 六个最容易丢分的细节
第一,依赖和关联混用。这是最普遍的错误。看到两个类有关系就直接画实线关联,忽略了依赖才是更合适的选择。判断依据再看一遍:被引用者是不是长期作为属性?如果是,关联;如果只在方法里临时出现,依赖。
第二,聚合和组合分不清。把“整体和部分可以分离”误判为组合,或者反过来。核心还是要抓住生命周期:组合强调“整体消失部分跟着消失”,聚合强调“部分可以独立存在”。复习时把“订单和订单项”、“人和大脑”归为组合,“班级和学生”、“图书馆和书”归为聚合,多背几组例子就能形成条件反射。
第三,多重性标注错误。多重性符号的含义要记牢:0..1是可以没有但最多一个,1..*是至少一个没有上限,*等价于0..*。考试时括号里经常会故意写反,比如“一个读者最多借5本书”,有人就写成5..*,正确的是0..5。做题时先找量词:“最多”“至少”“恰好”“多个”,再决定符号。
第四,可见性符号写错。+、-、#、~这四个符号是必考基本功。尤其是#(protected)很多人会忘记,它表示只有子类和同包类能访问。我见过不少答案是写反的,把private画成+,阅卷直接扣分。建议做题后统一检查一遍所有可见性符号。
第五,忽略关系方向。依赖、关联、泛化都有方向,箭头方向画反等于语义错误。泛化箭头必须从子类指向父类,实现必须从实现类指向接口。依赖箭头指向被依赖的那个类。画完图之后,自检一遍“每个箭头的方向是否符合语义”。
第六,把对象图当成类图。类图描述的是“类”这个模板的结构,对象图是某个时刻具体“实例”的快照。考题里如果给出student1: Student这种带对象名冒号类名的写法,是在画对象图。两者考点完全不同,别混为一谈。
5.2 简答题和画图题怎么答才稳
遇到“根据需求描述画类图”的题,我建议大家按这样的顺序作答,既不容易漏点,阅卷老师也容易给分:
先写类清单,表明你分析了哪些候选类,包括必要的接口;再写关系清单,逐条写出“A类和B类之间是什么关系,为什么”;最后画图。画图时类框内部属性方法栏必须完整,涉及属性的可见性、方法的参数和返回类型都标注清楚。关键关系线旁标注多重性和角色名。如果你的图上标了这些,就算某个关系判断有争议,部分分也能保住。
遇到“比较类图中某两种关系的区别”这类简答题,比如依赖和关联的区别、聚合和组合的区别,答题模板是:先各自给定义,再说画法区别,最后举一个对比实例。定义、画法、实例三件套齐全,这道题基本就是满分。
复习建议上,不要只看不画。找一个自己熟悉的场景,比如外卖点餐、在线选课、仓库管理,把“需求描述”写出来,然后自己独立画一遍类图,画完再去对照标准答案或者找同学互相检查。我这个方法亲测有效,考前一周每天画一套,到考试时看到类图题就像看到老朋友。
最后提醒一句:类图的本质是沟通工具,不是炫技。画图时多想想“这张图给别人看能不能看懂”,比死抠符号本身重要得多。复习时把这套思路学进去,不光考试能用,后面做课程设计、写毕业论文、进公司看架构文档,都会受益。