
类图这东西说难不难说简单也真不简单。我见过太多团队花了两周画出来一墙的方框和箭头结果开发同学一眼都不看代码该乱还是乱。也见过有人用 StarUML 类图把半个订单系统拆得清清楚楚评审会上半小时就对齐了边界后面返工极少。差别不在工具在于画之前有没有想清楚这张图是给谁看的、要解决什么问题。StarUML 是我这些年画 UML 类图用得最顺手的一个轻量、启动快、语法规范不用折腾环境装上就能开工。这篇东西我打算把我自己画类图的完整流程拆开讲从认知对齐、环境配置到类、接口、六种关系怎么落笔再到一张完整的订单系统类图实操最后是踩过的坑和排查方法。不管你是刚学 UML 的学生还是被要求画个类图的开发者照着走一遍应该都能上手。1. 动手之前先搞清楚类图到底在画什么1.1 三种使用场景决定你这张图该画多细很多人一上来就打开工具开始拖方框这是最容易翻车的起点。类图的详略程度完全取决于它的读者是谁我大致把它分成三种场景每种画法差别非常大。第一种是沟通对齐型通常出现在需求评审或者方案讨论阶段读者是产品、测试、后端、前端混在一起的一屋子人。这种图只保留领域对象和主干关系属性最多写到三五个关键字段方法基本不写。它的唯一目的是让所有人对系统里有哪些核心概念、它们之间怎么搭达成共识。画得越细讨论越容易跑偏到实现细节上。第二种是设计落地型读者是真正要写代码的人可能是你自己也可能是接手你模块的同事。这种图要把类的属性、方法签名、可见性、返回类型都写清楚关系要标明多重性1、0..、1..重要约束用注释标出来。它本质上是一份可以直接翻译成代码的设计说明书。第三种是文档归档型通常出现在项目验收、交接或者论文里。这种图追求规范、完整、好看要考虑图例、分层、配色、导出格式甚至要配上文字说明。StarUML 在这方面比较省心因为它输出的图形本身就是标准 UML 语法不会出现某些绘图工具画出来的野生 UML。1.2 类图真正的价值不在图本身而在被迫做的那些决策我特别想强调一点画类图的收益八成来自画的过程两成来自画完的结果。因为当你要把一个类拆成两个、要把一段继承改成组合、要决定某个字段该放在父类还是子类时你必须回答一些平时写代码时可以含糊过去的问题——这个职责到底属于谁这个变化点会不会扩散这两个模块是不是耦合太紧了这些问题在代码里藏着在图上藏不住。一个类的框里堆了二十个方法视觉上就刺眼一条关联线从订单连到用户又连回订单形成环一眼就能看出耦合有问题。工具本身不产生价值是这种被迫暴露产生了价值。StarUML 在这件事上有个天然优势它的模型树Model Explorer和图是联动的。你在树上加一个属性图上立刻出现你在图上删一个类树上也跟着没了。这种一一对应关系逼着你保持模型干净不像用纯图形工具那样画着画着图里的元素和脑子里的概念就对不上了。1.3 为什么我在几个候选工具里长期用 StarUML说实话能画 UML 类图的工具太多了。文本驱动的有 PlantUML、Mermaid绘图类的有 draw.io、Visio、ProcessOnIDE 集成的有 IDEA 的 Diagrams、Eclipse 的插件。我基本都用过最后长期留在 StarUML 的原因很朴素一是启动快、不吃资源。它是个体量很小的桌面应用打开基本是秒级我经常开着它当草稿纸用想到什么结构随手画两笔。这种低摩擦感很重要工具一旦重你就会懒得用。二是严格遵守 UML 规范。它不会让你画出语法上不成立的东西箭头、菱形、虚线的样式都是标准的那一套。这对需要交付正式文档的场景很关键。三是模型和视图分离。一个类可以同时出现在类图、时序图、状态图里改一处全部同步。这是纯绘图工具做不到的。四是支持正向和反向工程。能从模型生成 Java、C#、C、Python 等语言代码骨架也能从现有代码反向生成类图。接手老项目时这个功能救命。当然它也有短板协作能力弱多人同时改同一个文件会冲突自动布局不算聪明复杂图还是得手动摆部分高级功能需要装扩展。这些后面都会讲到。2. StarUML 的环境准备与界面速通2.1 获取与授权走正规渠道别在激活上浪费时间官网可以直接下载安装包Windows、macOS、Linux 都有。装完之后有一段试用期功能是全的只是会时不时提醒你。试用到期后的正规做法是走官方渠道获取个人授权学生和教学场景可以看看官网的教育政策说明。这块我不建议花心思绕一是费时间二是企业环境下用来源不明的版本本身就是合规风险。如果你的预算确实紧张或者只是偶尔画一两张图完全可以换文本方案。PlantUML 的类图语法很简洁写几行文本就能出图还能直接塞进版本控制里做 diff团队协作体验反而比二进制文件好。举个例子class Order { -String orderId -Date createTime BigDecimal calcTotal() } class OrderItem { -String skuName -int quantity } Order 1 *-- 1..* OrderItem这段代码表达的就是订单和订单项的组合关系。不过文本方案的短板是排版不可控图复杂了自动布局会很难看而且修改细节得改代码而不是拖鼠标。我的做法是草稿和需要进版本库的图用文本方案需要正式交付、精修排版的图用 StarUML。2.2 界面四大分区与建模的心智模型第一次打开 StarUML你会看到大致四个区域理解它们是理解整个工具的关键。左侧是 Model Explorer模型树。这是整个工具的心脏。所有元素——项目、模型、包、类、接口、属性、方法、关系——都以树的形式存在这里。图只是这个树的一种可视化呈现。养成一个习惯先看树再看图。当图上找不到某个元素时它八成还在树里只是被删掉了视图或者跑到别的图里去了。中间是画布。图都画在这里支持多标签页一个项目里可以有很多张图。画布上有网格和标尺对齐全靠它们。右侧是 Properties属性面板。选中任何元素它的所有可配置项都在这里。这是 StarUML 最不图形化的地方也是最省事的地方——很多细节在图面上点来点去改不动在属性面板里改一行就好。左上和上方是工具栏与菜单栏。工具箱里放着各种可拖拽的元素Class、Interface、Enumeration、Package以及六种关系工具。菜单栏里 Format 那个菜单要重点记住布局、大小、显示选项基本都藏在里面。2.3 开工前建议先调的几个默认配置有几个设置我每次装完都会先改能省掉后面大量重复劳动。第一是开启自动调整大小。在 Format 菜单里有自动调整的选项开启后类框会跟着内容长度自己变高变宽不会出现文字被截断的情况。手动拖框是效率杀手。第二是确认网格吸附。网格吸附能让类自动对齐画出来的图清爽很多。如果你的图看起来总是歪歪扭扭先检查这里。第三是想清楚显示粒度。在 Format 菜单下有控制属性、方法、可见性显示与否的选项。做高层概览图时把属性和方法都隐藏类就变成一个纯色块视觉上干净得多做详细设计时再全部打开。同一个模型换个显示设置就能出两种用途的图不用重画。第四是统一主题和字号。团队协作时建议约定一套配色比如实体类一个色、服务类一个色、接口一个色。字号保持默认或统一调大一档导出到文档里才看得清。注意StarUML 的工程文件是二进制或自定义格式的直接丢进 Git 做文本 diff 意义不大。多人协作时更靠谱的做法是分模块拆分文件或者干脆把图导出成图片再进版本库。3. 类图核心元素逐个拆解与实操3.1 类与接口三段式格子里到底该写什么拖一个 Class 到画布上你会看到一个分三段的矩形。别小看这三段每一段都有明确语义。第一段是类名。规范写法是首字母大写的名词单数比如Order、PaymentService。如果有抽象、接口、枚举等语义靠 stereotype构造型来标明在属性面板里设置。StarUML 会在类名上方用双尖括号显示出来。命名这块别偷懒OrderManager、OrderUtil、OrderHelper这类词该扔就扔它们在图上会立刻暴露职责不清的问题。第二段是属性。格式是可见性 名称: 类型。可见性用四个符号表示这是 UML 的固定约定符号可见性对应 Java 关键字使用建议publicpublic只用于常量或确实对外的成员-privateprivate字段默认用这个#protectedprotected只给子类用的成员~package默认无修饰符同包协作时使用第三段是方法。格式是可见性 名称(参数: 类型): 返回类型。参数多的话建议换行显示StarUML 支持在属性面板里逐条添加参数。这里有个特别容易踩的坑很多人在 Name 字段里直接写- orderId: String把可见性符号和类型全塞进去。这样做在图上看着没问题但一旦你要用这个模型生成代码生成的字段名就会变成一串乱七八糟的东西。正确做法是拆开填——Name 填orderIdType 选StringVisibility 选private图上会自动渲染成- orderId: String。这个细节决定了你的模型是一张图还是一份能用的设计。接口的写法略有不同。接口框里通常只写操作不写属性方法一般省略可见性符号因为接口方法默认都是公开的。StarUML 里有独立的 Interface 元素别用 Class 加个 stereotype 来冒充语义不一样。3.2 属性和方法的进阶写法基础会了之后有几个进阶细节值得单独拿出来说。静态成员在 UML 里用下划线表示。在 StarUML 属性面板里勾选 isStatic图上就会给这一行加下划线。工具类里的常量、工厂方法都该这样标。抽象操作要用斜体。勾选 isAbstract方法名会自动变成斜体。抽象类里的抽象方法、接口里的方法都用这个标法。泛型和模板类。StarUML 支持给类加模板参数属性面板里有 Template Parameters 之类的配置项。如果你的设计里用到RepositoryT这种结构别用Object糊过去老老实实标出来。构造函数的处理。这个争议挺大。我的做法是只有构造函数承担了非平凡的初始化逻辑时才画出来比如需要注入依赖、需要做参数校验、需要根据参数决定初始化哪个子类。如果只是简单的赋值省掉不画图会清爽很多。注释与约束。StarUML 里有 Note 元素可以拖到画布上写业务规则比如订单金额 所有订单项小计之和这种。图上写不下的约束条件用注释补比硬塞进方法名里强得多。3.3 六种关系的箭头语义对比这一段建议反复看关系是类图里最容易画错的部分也是面试和评审时最常被问的地方。我把六种关系整理成一张表建议对着图一起看。关系类型StarUML 工具名图形表示代码对应语义要点泛化继承Generalization实线 空心三角三角指向父类extendsis-a子类是一种父类实现Realization虚线 空心三角三角指向接口implements类履行接口契约关联Association实线可带方向箭头成员变量has-a长期持有聚合Aggregation实线 空心菱形菱形在整体端成员变量弱整体与部分可分离组合Composition实线 实心菱形菱形在整体端成员变量强生命周期绑定不可分依赖Dependency虚线 开放箭头箭头指向被依赖者局部变量、参数、静态调用临时使用不用长期持有泛化的方向千万别搞反。三角形的尖角永远指向父类因为子类知道父类父类不知道子类。这是里氏替换原则在图形上的体现。实现和泛化的区别只在线的虚实三角样式一样。虚线的意思是契约实线的意思是血缘这个比方我好几年了还在用。关联是最灵活的一种。方向可以双向也可以单向实际画图时优先画单向只有确实需要互相访问时才标双向。加箭头表示导航性加多重性表示数量关系加角色名表示这个端在对方语境里叫什么。聚合和组合的区别是初学者最容易糊的地方。判断标准很简单整体消失了部分还能独立存在吗汽车和轮胎车报废了轮胎还能拆下来卖这是聚合用空心菱形。订单和订单项订单被删了订单项没有任何独立意义这是组合用实心菱形。菱形永远画在整体那一端很多人第一次画都画反了。依赖是最弱的耦合通常表现在代码里是方法的参数、局部变量、静态方法调用。用虚线和开箭头。它不需要长期持有用完就走。3.4 三步判断法面对两个类该选哪种关系理论看完了真上手还是容易纠结。我总结了一个三步判断流程基本能覆盖九成场景。第一步问是不是同一种东西。如果 B 是 A 的一种特化比如退款单是一种订单用泛化。如果 B 只是承诺了 A 的行为而不共享实现用实现。第二步问是不是拥有关系。如果是进入第三步细分。如果不是拥有只是偶尔用一下那就是依赖。第三步问生命周期是否绑定。整体销毁时部分还能独立存在用聚合部分随着整体一起生灭用组合。举个完整的例子。Order和OrderItem订单项离开订单没有意义生命周期绑定用组合。Order和Customer一个客户可以有多个订单但删掉订单不会影响客户这是关联多重性在订单端标0..*在客户端标1。Order和DiscountCalculator订单计算价格时临时调用一下不持有引用用依赖。VipOrder和OrderVIP 订单是订单的一种用泛化。把这几种关系画在同一张图里对比一下你会发现视觉差异极其明显比看十遍文字描述都管用。这也是我一直建议初学者练手的方法先照着表格把六种关系各画一个最小示例放在一张图上标上名字。4. 完整实操从需求到一张订单系统类图4.1 从需求描述里抽名词再筛掉一半我拿一个简化版的订单需求做例子用户可以下订单订单里包含多个商品项每个商品项有数量和单价订单支持普通订单和 VIP 订单两种VIP 订单有额外折扣订单金额需要能计算出来支付通过第三方的支付网关完成。第一步是抽名词。把所有名词列出来用户、订单、商品项、商品、数量、单价、普通订单、VIP 订单、折扣、订单金额、支付网关。这一步别管对错先把候选都扔出来。第二步是筛选。判断标准有两条这个名词是否在系统里有独立身份和行为这个名词是否会被多个地方引用按这个标准过一遍用户在订单系统里只是个外键引用如果没有用户管理的职责可以精简成一个Customer值对象甚至就留个 ID。商品通常属于另一个限界上下文这里只需要skuId和skuName不要画一个完整的Product类然后挂一堆属性。数量和单价是OrderItem的属性不是独立类删掉。订单金额是计算结果不是类删掉。支付网关看起来像个外部系统但通过它支付这个动作意味着它是个接口契约抽象成PaymentGateway接口比较合适。筛完之后剩下的核心类大概是Order抽象、NormalOrder、VipOrder、OrderItem、Customer、PaymentGateway接口。4.2 落图顺序类、属性、关系、修饰一步一步来第一步先把所有类拖到画布上摆成大致的层次。抽象基类放上方或左侧具体类放在它下方接口放在左侧边缘值对象靠近使用它的类。这个时候不要连线纯粹摆位置。位置摆好了后面连线会顺很多。第二步用模型树批量填属性。在这一步我强烈建议用左侧的 Model Explorer 而不是在图面上点。因为实体类字段往往有七八个在树上右键 Add Attribute 然后填 Name、Type、Visibility比在图上双击、定位、输入快得多。填的时候注意前面说的坑别把符号和类型混进 Name 里。Order的属性大概是orderId: String、createTime: Date、status: OrderStatus、customer: Customer、items: ListOrderItem。方法上放一个抽象方法calcTotal(): BigDecimal。OrderItem属性skuId: String、skuName: String、quantity: int、unitPrice: BigDecimal。方法subtotal(): BigDecimal。VipOrder属性discountRate: BigDecimal。它重写calcTotal()。PaymentGateway接口方法pay(order: Order, amount: BigDecimal): boolean。第三步连线。这一步有个操作技巧值得单独讲用鼠标右键从一个类的边框拖到另一个类会比左键拖拽稳定得多也不容易误触发选择框。拖的时候起点一定要落在类的边框上落在框内部是画不出关系的。连线顺序建议是先画泛化和实现骨架关系再画组合和聚合结构关系最后画关联和依赖协作关系。这样画出来的图层次清楚也方便你检查。把六种关系代入这张图NormalOrder和VipOrder都用泛化指向Order。Order用实现指向PaymentGateway不对这里要注意——订单本身不实现支付接口是订单使用支付接口。所以这里应该是依赖或者关联。如果你想要支付服务实现支付网关接口的语义那要再加一个PaymentService类让它实现PaymentGateway。这个判断过程本身就说明类图在帮你理清职责。Order和OrderItem是组合菱形在Order端多重性1..*。Order和Customer是关联Order端标0..*Customer端标1。4.3 布局与可读性让图看着专业的那几个细节模型对了不代表图好看。我见过很多语义完全正确的类图因为布局糟乱评审时所有人都在找线效率极低。几个我常用的处理手法按层分区。用 Package 或者简单的矩形框把图分成几块比如领域模型服务外部接口。StarUML 里可以用 Package 元素把类包起来视觉上立刻有层次。减少交叉。两条关系线交叉是没办法的事但交叉五条以上就该重新摆位置了。原则是把关系密集的类放在一起关系稀疏的放远。统一类框宽度。宽度参差不齐看起来很难受。选中所有类用 Format 菜单下的对齐和统一大小功能处理一下。多重性和角色名要标在正确的位置。多重性标在线的两端靠近对应的类角色名也是。标错位置会让人误解谁对应谁。线的端点要吸附到边框。StarUML 里关系线的端点如果不慎落在了类框内部看起来会很别扭。选中线拖动端点重新吸附即可。控制单张图的元素数量。我的经验是一张类图不超过 15 个类超了就拆。拆的原则是按限界上下文或者按层次拆一张画领域模型一张画服务层用注释互相引用。4.4 导出、嵌入文档与代码骨架生成图画完总得用起来。StarUML 的导出在 File 菜单下的 Export As 里支持 PNG、JPEG、SVG 几种格式。需要嵌进 Word 或者 Confluence 的选 PNG需要缩放的选 SVG。导出前记得先全选所有元素用自动调整大小处理一遍否则边缘容易被裁掉。如果图很大导出时注意选择合适的分辨率倍数。代码生成这块需要先在扩展管理器里装上对应语言的扩展然后在 Tools 菜单下找到生成入口。生成的结果是这样的public abstract class Order { private String orderId; private Date createTime; private OrderStatus status; private Customer customer; private ListOrderItem items; public abstract BigDecimal calcTotal(); } public class VipOrder extends Order { private BigDecimal discountRate; Override public BigDecimal calcTotal() { // TODO return null; } }注意生成的是骨架不是能跑的代码。方法体都是空的注释是占位符。它的价值在于帮你把结构和签名一次性铺好省掉重复劳动避免手写时漏字段或者签名不一致。生成完之后一定要人工过一遍尤其是泛型、注解、构造器这些工具处理不好的地方别指望它一步到位。5. 常见问题与排查技巧实录5.1 操作层面的高频问题速查这些是我自己被问得最多、也最常自己遇到的问题整理成表方便对照。现象真实原因解决方式关系线怎么都画不出来起点落在了类框内部没落在边框上从边框处用右键拖拽到目标类箭头方向反了模型树里 Source 和 Target 搞反了选中关系在属性面板里对调两端想要实线却出来虚线用了 Dependency 工具删掉重画改用 Association类的文字被框截断没开启自动调整大小全选后执行自动调整或手动拖大属性在图上是乱码一样的字符串Name 字段里塞了可见性和类型拆成 Name、Type、Visibility 三处填菱形画到了错误的一端图形的整体端判断反了选中关系把菱形端点拖到整体那一端导出的图片边缘被切画布边界没包住内容全选元素后调整画布或导出时扩大范围改了模型树图上没变化元素没有添加到当前图里从模型树把它拖到画布上再补几条经验型的技巧这些在官方文档里基本找不到。撤销要常用。StarUML 的撤销栈挺深画错了直接撤销比手动改快。特别是连错关系的时候。善用复制粘贴。结构相似的两个类先建好一个复制一个过去改名字比从零拖快得多。复制的元素是独立的改一个不会影响另一个。模型树里的东西删了就是真删了。在图上按 Delete有可能只是从当前视图中移除元素还在树里在树里删就是彻底删除。搞清楚这个区别能避免我的类找不到了的恐慌。多图共享同一个模型。同一个类可以在领域模型图和详细设计图里同时出现改属性两边都变。这是 StarUML 相对纯绘图工具最大的效率优势一定要用起来。5.2 语义层面的判断难题操作问题好解决语义问题才是真正费脑子的。挑三个我纠结过最久的说说。第一个继承还是组合判断标准是is-a 还是 has-a但实际项目中经常出现两可的情况。我的经验规则是只要子类需要选择性继承父类的部分行为那就别用继承。继承是全盘接受一旦父类有子类不需要的方法就是设计味道。另外继承层级别超过三层超过就该考虑用组合拆了。第二个关联要不要画双向初学者喜欢画双向觉得信息更全。实际上双向关联在代码里意味着两个类互相持有引用这是强耦合。只画真正需要的那一个方向需要双向访问时先想想能不能通过中间对象或者事件解耦。第三个多重性到底怎么标语法上1、0..1、1..*、0..*、*都合法。我的习惯是用1表示必然有且只有一个0..1表示可选1..*表示至少一个0..*表示任意数量。这个标注不是装饰它直接影响代码里的空值判断和集合初始化标准确了能省掉很多边界 bug。5.3 团队协作时的版本管理坑StarUML 的工程文件是二进制或者自定义格式这意味着两件事不能做有意义的文本 diff不能顺利合并。两个人同时改同一个文件最后只能一个人覆盖另一个人。我们团队的应对方式是三条一是按模块拆分文件比如订单模块一个文件、支付模块一个文件减少冲突面二是指定单一维护人其他人提修改意见而不直接动文件三是导出 PNG 进版本库作为历史快照和评审依据源文件单独管理。如果你的团队对协作要求很高说实话文本方案PlantUML 这类在这种场景下体验会好很多因为纯文本能 diff、能合并、能 review。可以两种并存用文本格式存主干设计用 StarUML 做精修和交付。6. 让类图活下来后续还能这样用6.1 反向工程接手老项目时的救命功能接手一个没有文档的老项目时最快的理解方式是从代码反向生成类图。StarUML 装好对应语言的扩展后可以在 Tools 菜单里找到反向工程入口选中源码目录就能生成模型。生成之后别急着看全貌先按包过滤只留核心的几块否则几百个类堆在一张图上完全没法看。反向出来的图有几个典型问题要手动清理一是继承层级可能很深很乱把无意义的中间层合并掉二是工具分不清关联和依赖往往全都画成关联需要人工降级三是注释和业务规则丢失这部分只能靠读代码补。我的用法是反向生成 → 按模块裁剪 → 补注释 → 作为重构前的现状快照。有了这个基线后面每做一次重构都能对比着看结构是不是朝预期的方向变了。6.2 一份可以直接抄的类图自检清单图交给别人之前我都会对着下面这几条过一遍能拦掉大部分低级问题。每个类名都是名词单数吗有没有Manager、Util、Helper这类含糊词每个类的职责能用一句话说清吗说不清就该拆。抽象类是否真的有多个子类只有一个子类的抽象层是过度设计。关系方向都正确吗三角指向父类菱形在整体端依赖箭头指向被依赖者。关联的多重性都标了吗有没有漏标导致歧义有没有同一个语义既画了关联又写了属性重复表达必须删一个。泛化层级超过三层了吗有没有双向关联是可以降为单向的一张图的类数量是否超过 15 个关键业务约束有没有用注释标出来最后再说一个我自己踩过的坑。早年我特别迷恋把类图画得极其详尽属性方法一个不漏连 getter 和 setter 都画上。结果那张图后来没人看因为信息噪音太大读者找不到重点。现在我画图会做减法默认隐藏所有 getter、setter 和简单的构造器只留业务方法和关键字段。需要的时候再按显示设置打开。一张图的信息密度控制到读者能在三十秒内说出这张图在讲什么就够了。