接手一个半成品模型,打开一看,类图铺满整屏,连线像蛛网,类名有中文有英文还有拼音缩写,属性类型一半是String一半是string还有varchar(50),那一刻我脑子里只有一个念头:这图不是用来设计的,是用来考古的。PowerDesigner 这类建模工具,很多人第一次接触是在课堂上画个图书管理系统,第二次接触就是接手别人的烂摊子。类图设计整理这件事,看起来是"把图摆整齐",实际干的是把团队的建模语言统一、把隐性的设计约定显性化、把模型从"示意图"拉回"可生成代码的工程资产"。
这篇东西写给三类人:刚上手 PowerDesigner、需要把课程作业或者项目文档里的类图画规范的新手;接手遗留模型、被一堆冗余关系和重复类折磨的中级开发者;以及需要在团队里推行建模规范、但苦于没有可落地清单的技术负责人。下面所有内容都围绕一件事展开——怎么在一张类图上,把结构、命名、关系、校验、输出这几件事一次性理顺,让它既能给人看,又能给机器用。
1. 类图整理到底在整理什么
1.1 一张类图最容易烂掉的三个地方
我见过的烂模型,问题高度集中在三个位置,几乎不例外。第一个是命名双轨制失控。PowerDesigner 里每个对象都有 Name 和 Code 两个字段,Name 给人看,Code 给机器用。新手往往只填 Name,Code 让它自动生成,于是中文名变成一串拼音,改了三次名字之后 Code 彻底对不上,生成代码时类名全是Lei1、Lei2。第二个是关系语义漂移。泛化、实现、关联、聚合、组合、依赖,六种线画出来长得差不多,很多人凭手感连,最后组合关系画成了普通关联,聚合和组合分不清,代码生成出来的字段类型和实际设计意图完全两回事。第三个是"图即一切"的错觉。类图只是模型的一个视图,真正的信息存在模型仓库里,可很多人把它当成 Visio 用,画完导出一张 PNG 就交差,属性类型、多重性、导航性全部缺失,后面既生成不了代码,也转不成数据库模型。
这三个问题的共同根源,是把类图当成"沟通插图"而不是"设计源文件"。一旦你接受"这张图要拿去生成代码、要参与评审、要在半年后被别人接手"这个前提,整理的标准立刻就清晰了:可读、可校验、可导出。
1.2 先把"图"和"模型"分开看
这是我认为最值钱的一个认知转换。在 PowerDesigner 里打开一个 OOM(Object-Oriented Model),左侧浏览器树里是一堆 Package、Class、Interface,右侧画布是 Class Diagram。画布上删掉一个类的图形,模型里的类还在;反过来在浏览器里删掉类,画布上的图形和它相关的连线会一起消失。搞不清这层关系的人,经常在画布上拖来拖去,以为整理完了,结果模型里躺着一堆孤儿类。
所以整理的顺序应该是自下而上的:先处理模型树里的对象——去重、改名、补属性、定类型;再处理画布上的布局——分组、对齐、减少交叉。反过来做,等于给一堆没整理好的数据做排版,白费功夫。
注意:整理前先做一次完整备份,另存为一个带日期后缀的副本文件。PowerDesigner 的撤销栈对模型级操作支持有限,批量改 Code 这类动作一旦做错,回退成本极高。
2. 建模前的基线设置:别让默认值坑你
2.1 新建 OOM 模型时必须改的几个选项
很多人建模型时一路下一步,默认语言选 Java,然后就开工了。默认值本身没错,但有几个开关不调,后面全是坑。
建模型时在 Model 名称之外,重点看Model Options(菜单 Model > Model Options)。在 OOM 分类下,有这几项值得动:
| 选项位置 | 默认行为 | 建议调整 | 原因 |
|---|---|---|---|
| Model Settings > Name/Code case | 自动同步 | 关闭自动同步 | 避免改名时 Code 被悄悄改掉,破坏已有代码映射 |
| Model Settings > Generate Java import | 关闭 | 打开 | 生成代码时自动补 import,省手工 |
| Data Types > 默认类型映射 | 通用类型 | 绑定具体语言类型 | 让String就是java.lang.String,不是自定义字符串 |
| Attributes/Operations > 默认可见性 | public | 按分层约定设置 | 实体类属性默认 private,减少人工改 |
关闭 Name/Code 自动同步这一条,我要单独强调。默认情况下你把 Name 从"订单"改成"销售订单",Code 可能跟着变,而你之前生成的代码里类名是Order,一改就对不上了。关掉同步后,Name 随便改,Code 保持稳定,这是模型能长期维护的前提。
2.2 命名基线与数据类型映射
命名这件事,团队里必须先吵一次架再定下来,否则后面每次评审都要吵。我给的建议是三句话:Name 用业务语言,允许中文和空格;Code 用英文驼峰,全局唯一;包名和目录结构对齐。
具体落地时,在 Model Options 里设置好 Name/Code 的大小写规则,然后在模型里建一个"词汇表"包,把所有核心概念列一遍:订单、客户、商品、支付单。这一步看着多余,实际是防止同一个人在不同包里把"客户"写成 Customer、Client、Kunde 三种。我做过统计,一个两万行规模的系统,如果前期没有词汇表,类图里出现同义类的概率大概在三成以上。
数据类型方面,别在每个属性上手写java.lang.String。用Domain(域)来统一管理:建一个 Domain 叫Name,绑定String,长度约束写在注释里;建一个Money,绑定BigDecimal,精度 2 位;建一个Id,绑定Long。以后所有属性引用 Domain,改一处全局生效。这跟数据库里用用户自定义类型是一个思路,投入产出比极高。
3. 类的整理:从一坨方块到可读结构
3.1 类的分层与分包策略
打开画布第一件事不是摆位置,是分包。PowerDesigner 的 Package 是可以嵌套的,用好了就是天然的模块边界。我习惯按三层切:边界层放对外接口和 DTO,应用层放用例编排,领域层放实体和值对象。每层一个 Package,Code 直接写成对应的包路径,比如com.demo.order.domain。
分包之后有个立竿见影的好处:画布上可以用 Package 的折叠视图,一张图只展开当前关注的那一层,剩下的收起来。评审的时候按层过,一次只看十几个类,认知负担立刻降下来。
包的依赖方向也要顺手定死:边界层依赖应用层,应用层依赖领域层,领域层不依赖任何人。这条规则写在包的注释里,后面用 Check Model 或者人工评审都能查。我见过最典型的坏味道,是领域实体里直接引用了 Controller 层的类,模型上一根虚线箭头横跨三个包,代码里就是循环依赖。
3.2 属性与操作的规范化写法
属性整理有个很实用的顺序:先补类型,再定可见性,最后加多重性。类型用 Domain 引用,可见性按分层约定,多重性处理集合。
集合属性是重灾区。很多人把List<OrderItem>写成属性类型OrderItem,多重性留空,生成代码时就变成一个对象。正确做法是类型写OrderItem,多重性写0..*,PowerDesigner 生成 Java 代码时会自动变成List<OrderItem>或者数组。这个映射在 Model Options 的 Java 分类里能配,默认是集合类型,也可以改成数组。
操作(Operation)方面,别在类图里写实现,但签名要完整。返回值类型、参数名、参数类型都要填。参数类型同样引用 Domain 或者已有类。我踩过的一个坑:参数类型留空,生成代码时是Object,编译能过但等于没设计。整理的时候用 Check Model 能扫出一批"无类型参数",一次改完。
还有个小技巧,抽象类和接口要打上对应的构造型(Stereotype)。接口用Interface图标或者<<interface>>,抽象类在类的属性里勾选 Abstract。这些小标记在代码生成和评审时都会被识别,省得别人猜。
3.3 去重与合并的实操判断
整理遗留模型最耗时的环节是去重。画布上看着两个类不一样,点进去发现属性有八成重合。我的判断流程是这样的:先比属性集合,重合度超过七成且语义一致,考虑合并;然后看引用关系,被引用的地方越多,合并的影响面越大,越要谨慎;最后看生命周期,如果一个类的实例随另一个类创建销毁,那是组合不是重复。
合并操作在 PowerDesigner 里没有一键按钮,只能手工把属性搬过去,然后删掉多余类。删之前一定要用Edit > Find搜一遍这个类的 Code,确认没有别的关系线挂着。我吃过亏:删了一个类,画布上看着干净了,结果模型里还有两条关联指向它,生成代码时报空指针,查了半天才发现是孤儿关系。
4. 关系的整理:四种连线别乱用
4.1 泛化、实现、关联、依赖的判定顺序
关系线是最容易画错的部分。我给团队的要求是:画线之前先在心里过一个四问决策链。
第一问:是不是 is-a 关系?是的话,目标如果是类就是泛化(Generalization,实线空心三角),是接口就是实现(Realization,虚线空心三角)。这一问能挡掉大部分误用。
第二问:是不是整体-部分?是的话再看生命周期。整体销毁部分跟着销毁,用组合(Composition,实线实心菱形);部分可以独立存在,用聚合(Aggregation,实线空心菱形)。订单和订单项是组合,订单项删了就没意义;部门和员工是聚合,员工离职部门还在。
第三问:是不是长期持有引用?是的话用关联(Association,实线,可加导航箭头)。比如订单持有客户引用。
第四问:是不是只在方法参数或者局部变量里用?是的话用依赖(Dependency,虚线箭头)。比如订单服务调用支付网关。
这个顺序的价值在于它把主观判断变成了可复述的规则。评审的时候不用争"我觉得这是组合",直接问生命周期,答案就出来了。
4.2 关联类、多重性与导航性的细节
多重性(Multiplicity)经常被忽略,但它直接决定生成代码的结构。常见的取值就五个:1、0..1、1..*、0..*、*。我的经验是:默认写1,只有确认可以为空才写0..1,集合一律0..*而不是*,因为*在很多语言映射里会被当成无约束,语义模糊。
导航性(Navigability)也值得说。单向关联画一个箭头,双向关联画两个箭头或者不画箭头。问题是很多人画了双向箭头,生成代码时只在一个类里生成了字段,另一个类没有,模型和代码就脱节了。整理时统一约定:模型上只画单向导航,需要反向查询的通过仓储或者查询方法解决。这样图和代码能一一对应。
多对多且有业务属性的时候,用关联类(Association Class)。比如学生和课程多对多,中间有个"选课记录"带成绩和时间,这时候别在两边各挂一个集合就完事,老老实实建一个关联类连上去,生成代码时就是一个独立的实体。这个习惯能省掉后面大量的中间表设计争论。
5. 校验、导出与协作
5.1 用 Check Model 把低级错误挡在提交前
PowerDesigner 自带模型检查,菜单在Tools > Check Model。这个功能被严重低估,很多人压根没用过。它能扫出来的问题包括:类没有 Code、Code 重复、属性没有类型、关系两端多重性缺失、孤儿对象、循环继承等等。
我的做法是把检查项固化成一张清单,每次提交模型前跑一遍,只勾选关键项:
| 检查项 | 严重程度 | 处理方式 |
|---|---|---|
| Object has no code | 高 | 补 Code,按命名基线填 |
| Duplicate code | 高 | 合并或重命名 |
| Attribute has no data type | 高 | 引用 Domain |
| Relationship has no multiplicity | 中 | 补1或0..* |
| Circular generalization | 高 | 拆继承链 |
| Unused object | 低 | 确认后删除 |
跑完检查会生成一个结果列表,双击能直接定位到对象。这个流程走顺了,模型质量能提升一大截,而且是机器保证的,不依赖人的记性。
5.2 代码与文档的自动化输出
模型整理到位之后,输出就是顺带的事。生成 Java 代码走Language > Generate Java Code,勾选生成默认构造、getter/setter、注释。注意生成前选好目标目录,别覆盖已有代码——PowerDesigner 默认会覆盖同名文件,我建议先生成到一个临时目录,用文件比对工具过一遍差异再合并。
生成数据库物理模型走Tools > Generate Physical Data Model。这里有个前提认知:类图不是数据库设计图。实体类转表比较自然,但值对象、枚举、继承结构转表都需要额外决策。我一般只把领域层的实体类转过去,继承用单表或者 joined 策略在生成后手工调整,别指望一键完美。
文档输出走Report > Report Wizard,能生成 RTF 或者 HTML 格式的模型说明,包含类清单、属性表、关系表。评审前导一份出来,比让人装 PowerDesigner 看模型友好得多。导出图片走Edit > Export Image,分辨率调到 300 DPI 以上,否则打印出来属性名全是糊的。
6. 常见问题与排查实录
6.1 高频问题速查表
整理模型这几年,遇到的问题翻来覆去就那些。我把高频的整理成一张表,遇到直接查。
| 现象 | 大概率原因 | 解决办法 |
|---|---|---|
| 改了 Name,生成代码类名没变 | Name/Code 未同步或已关闭同步 | 手动改 Code 字段 |
| 中文显示成方块或乱码 | 画布字体不支持中文 | Tools > General Options > Fonts 换字体 |
| 生成代码缺 import | 未开启自动 import | Model Options 里打开对应开关 |
| 关系线交叉严重看不清 | 未做自动布局 | Symbol > Auto-Layout,或按包分区手工摆 |
| 模型打开极慢 | 单文件类数量过多 | 按模块拆成多个 OOM,用 Package 引用 |
| 删除类后生成报错 | 残留孤儿关系 | 在浏览器树里删,不在画布上删 |
| 复制粘贴后 Code 重复 | 粘贴时 Code 未自动加后缀 | 批量选中改 Code,或跑 Check Model |
| 反向工程后类名带下划线 | 代码里用了下划线命名 | 反向工程设置里配命名转换规则 |
6.2 几个踩过的坑
坑一:在画布上整理布局,忘了同步模型。有次我把十几个类拖到不同区域,视觉上清爽了,但包的归属完全没变,生成代码时全在一个包里。后来养成习惯:先在浏览器树里挪包,再回画布摆位置。
坑二:反向工程直接覆盖。拿已有代码反向生成模型,图省事选了覆盖,结果手工补的注释和多重性全没了。正确姿势是新建一个模型做反向工程,然后用Tools > Compare/Merge Models比对差异,人工决定哪些合并。比对功能需要 Repository 或者两个模型文件,配置起来有点门槛,但比人工肉眼比强太多。
坑三:多人同时改一个模型文件。PowerDesigner 的模型文件是单个二进制文件,两个人同时改必然冲突,后保存的覆盖前面的。没有 Repository 的团队,我的建议是分模块拆文件,每人负责自己的 OOM,用包引用串起来;或者约定同一时间只有一个人有编辑权,其他人只读。
坑四:把类图当数据库设计图用。见过有团队直接在类图上标注表名、字段长度、索引,然后一键转 PDM。转出来的表结构一塌糊涂,因为类图的继承、组合、枚举在关系模型里没有直接对应。类图和物理数据模型是两个层次的东西,中间需要一次人工映射决策,这一步省不掉。
最后分享一个我自己一直在用的整理节奏:每次动模型之前,先花十分钟只做一件事——把当前模型的类数量、关系数量、未命名对象数量数一遍,记在模型文件的注释里。改完之后再数一遍。数字对不上,说明有东西被误删或者漏改。这个方法很土,但比任何检查工具都直接,尤其适合接手别人模型的第一天。