PlantUML 类图高级用法:抽象、接口、关联、依赖、限定符
类图是最常用却最容易被画错的 UML 图。零基础谁都看得懂,复杂了经常因为箭头画错让评审反复确认。这是一篇把类图所有关系一次说清楚的文章。
类成员可见性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @startuml class User { -id: Long -name: String #email: String ~passwordHash: String +createdAt: LocalDate +login() +logout() {static} +hashPassword() {abstract} #validateEmail() } note right of User - private (减号) # protected (井号) ~ package (波浪号) + public (加号) {static} 静态 {abstract} 抽象 end note @enduml
修饰
含义
-
private
#
protected
~
package / internal
+
public
{static}
静态方法
{abstract}
抽象方法
{method}
普通方法(一般省略)
类、抽象类、接口、枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @startuml abstract class Animal { +name: String {abstract} +makeSound() } class Dog { +breed: String +makeSound() } class Cat { +indoor: boolean +makeSound() } interface Pet { +play() } interface Named { +getName(): String } enum Color { BLACK WHITE TABBY } Animal <|-- Dog Animal <|-- Cat Animal ..|> Named Pet ..|> Dog Pet ..|> Cat @enduml
要点:
abstract class 抽象类,渲染成斜体
class 普通类
interface 接口,<> stereotype + 圆头
enum 枚举
<|-- 继承(实线 + 空心三角)
..|> 实现(虚线 + 空心三角)
类之间的关系:5 种 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @startuml class Car class Engine class Driver class Wheel class SteeringWheel Car "1" *-- "4" Wheel : 整体-部分 Car "1" o-- "1" Engine : 弱聚合 Car "1" --> "1" SteeringWheel : 关联 Driver --> Car : 依赖 Car ..> Driver : 泛化(少见) note bottom of Car *-- : composition(组合)— 整体消失则部分消失 o-- : aggregation(聚合)— 整体消失部分可以单独存在 --> : association(关联)— 长期持有关系 ..> : dependency(依赖)— 临时使用 --|> : generalization(泛化)— 子类 ..|> : realization(实现)— 接口 end note @enduml
记忆口诀:
关系
关键字
含义
代码表现
组合
*--
整体 / 部分,整体消失则部分消失
内部 new
聚合
o--
整体 / 聚合,整体消失部分独立
构造器注入
关联
-->
长期持有
字段引用
依赖
..>
临时使用
方法参数 / 局部变量
泛化
--|>
子类继承父类
extends
实现
..|>
类实现接口
implements
关联上的修饰:限定符、角色、可见性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @startuml class Company class Employee Company "1..*" o-- "*" Employee : employs note right of Company 1..* 表示多重性(multiplicity) 角色名 employs 是关联的名字 - 紧挨着 Employee 的位置可以标可见性(如 + - #) end note Company --> "- boss +name" Employee : 角色与可见性 @enduml
多重性是 min..max:
1 恰好 1
0..1 0 或 1
* 0 或多(不指定上限)
1..* 至少 1
角色名用括号写靠近目标类:- boss 表示 “Employee 在 Company 上是 private 的 boss 角色”。
嵌套类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @startuml class Order { -id: Long -items: List<Item> +checkout() } class OrderStatus { +PENDING +PAID +SHIPPED +DELIVERED } Order +-- OrderStatus : 内嵌枚举 @enduml
+-- 表示 Order contain OrderStatus,渲染成 inner+outer 嵌套。
包依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @startuml package "com.app" { package "domain" { class Order class Customer } package "service" { class OrderService class CustomerService } package "controller" { class OrderController } } OrderService --> Order : 引用 OrderController --> OrderService : 调用 CustomerService --> Customer : 引用 @enduml
package 多层嵌套,渲染成嵌套矩形。多用于「依赖图」「模块拓扑」。
抽象方法和实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @startuml abstract class Handler { {abstract} +handle(req: Request): Response +next(req: Request): Response } class AuthHandler { +handle(req: Request) } class LoggingHandler { +handle(req: Request) } Handler <|-- AuthHandler Handler <|-- LoggingHandler note bottom of Handler {abstract} 标识抽象方法 子类必须 override end note @enduml
组合 vs 聚合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @startuml class House { -address: String +rooms: List<Room> } class Room { -area: int } class Garage House "1" *-- "*" Room : 组合 House "0..1" o-- "0..1" Garage : 聚合 note right of Garage 组合:House 拆除后 Room 不存在 聚合:House 拆除后 Garage 可以挪走再用 end note @enduml
经验:判断是组合还是聚合,就问「整体不存在了,部分能不能独立存在」——能独立就用 o-- 聚合,必须依赖就用 *-- 组合。
关联类(中间表) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @startuml class Student class Course class Enrollment { -grade: Float -enrolledAt: Date } Student "1" --- "*" Enrollment Course "1" --- "*" Enrollment (Student, Course) .. Enrollment note right of Enrollment Enrollment 是 Student-Course 关联类 它有自己的属性(grade、enrolledAt) end note @enduml
关联类用 --- 关联两边,再 .. 引到中间类。常见于「关联有自己属性」的场景,如 Enrollment / 选课记录。
完整实战:电商订单领域模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 @startuml skinparam classAttributeIconSize 0 abstract class AggregateRoot { +id: Long {abstract} +validate() } class Order { -status: OrderStatus -items: List<OrderItem> -customerId: Long +place() +cancel() +ship() +complete() +totalAmount(): Money } class OrderItem { -productId: Long -quantity: int -unitPrice: Money } class Money { -amount: BigDecimal -currency: String } class Customer { -name: String -email: String } class Product { -sku: String -name: String -price: Money } enum OrderStatus { PENDING PAID SHIPPED DELIVERED CANCELLED REFUNDED } interface Auditable { +createdAt: Instant +updatedAt: Instant } AggregateRoot <|-- Order Auditable ..|> Order Auditable ..|> Customer Order "1" *-- "1..*" OrderItem OrderItem "1" --> "1" Product Order "1" --> "1" Customer Order "1" --> "1" OrderStatus OrderItem "1" --> "1" Money Product "1" --> "1" Money note bottom of Order 订单模型核心要点: - Order 是聚合根 - 包含多个 OrderItem - 引用 Customer 和 Product 但不持有 - 状态枚举集中表达生命周期 end note @enduml
评审 checklist
常见反模式 关系过度 1 2 3 4 5 6 7 class A class B class C A --> B B --> C C --> A @enduml
A → B → C → A 循环依赖。一张图同时画「运行时依赖」和「编译时依赖」会让评审混乱。
省略可见性 1 2 3 4 5 6 class User { id name email login() }
没标可见性等于让评审自己猜「这些字段 public 还是 private」。所有字段都标 。
互相依赖 1 2 3 4 5 class Foo class Bar Foo --> Bar : holds Bar --> Foo : holds
互引用经常意味着该拆模块,或两个类应该合并。
一句话总结 类图 5 种关系 + 4 种可见性 + 5 种结构类型,构成 UML 类图的全部。**关系方向、可见性、语义(聚合 vs 组合 vs 关联)**三件事画对,类图就专业了。