最近在绘制类图的时候,发现图时不能按照准确的描述类之间的关系,遂整理类图与面向对象的关系
类图:描述类及其内部结构和操作,以及类间的静态关系
对象图:描述运行时特定对象结构的示意图
交互图:展示对象间请求流程的一种示意图

描述面向对象关系:面向对象编程是基于类实现的,类体现了对对象的总结,包含事物的数据和行为。类与类之间的协作或者关系有如下六种:

  • 依赖关系
  • 关联关系
  • 聚合关系
  • 组合关系
  • 继承关系
  • 实现关系

1.依赖关系

1.1 依赖关系的定义

依赖关系表示一个元素(类、接口)等的变化可能会影响另外一个元素。它是临时性的、较弱的关系。通常出现在方法参数、返回值、局部变量或方法调用上。

1.2 uml表达依赖关系

在UML类图中,用虚线箭头“—>”表示依赖关系,由依赖方指向被依赖方
uml中用..>描述依赖,如果是 A依赖B, 则记作 A ..> B
在关系之间使用标签来说明时, 使用 :后接 标签文字
语义:

  • A依赖B意味着A在某处使用了B,B的变化会影响A
  • B不是A的成员变量

1.3 依赖关系示例代码

  1. 方法参数依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type PaymentGateway interface {
    Charge(amount float64) error
    }

    type OrderService struct{}

    // OrderService 依赖 PaymentGateway(通过方法参数)
    func (s *OrderService) Process(order Order, gateway PaymentGateway) error {
    return gateway.Charge(order.Amount)
    }

1.4 依赖关系示例类图

1
2
3
4
5
6
7
8
9
10
11
12
class OrderService {
+Process(Order, PaymentGateway) error
}
interface PaymentGateway {
+Charge(float64) error
}
class Order {
+Amount float64
}

OrderService ..> PaymentGateway : 依赖
OrderService ..> Order : 依赖

2 关联关系

2.1 关联关系的定义

关联关系描述的是两个结构体或着类之间的长期关系,通常表现为:

  • 结构体a持有另一个结构体的引用作为字段
  • 关系可以是单向关系或双向关系
  • 比依赖关系跟强,比组合/聚合关系弱

2.2 uml表达

在UML类图中,以实线箭头表示,箭头指向被关联的类
对应的uml表达为 -->

2.3.1 单向关联示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type User struct {
Name string
}

type Blog struct {
Title string
Author *User // Blog 关联 User(单向)
}

func main() {
user := &User{Name: "Alice"}
blog := &Blog{Title: "Go Tips", Author: user}
fmt.Println(blog.Author.Name) // 输出: Alice
}

2.3.2 单向关联示例类图

1
2
3
4
5
6
7
8
class Blog {
+Title string
+Author *User
}
class User {
+Name string
}
Blog --> User : 关联

2.4.1 双向关联示例代码

学生和课程相互持有,形成双向关联
通常在orm多对多的关系中出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Student struct {
Name string
Courses []*Course // Student 关联 Course
}

type Course struct {
Name string
Students []*Student // Course 关联 Student
}

func main() {
alice := &Student{Name: "Alice"}
math := &Course{Name: "Math"}

alice.Courses = append(alice.Courses, math)
math.Students = append(math.Students, alice)
}

2.4.2 双向关联示例类图

1
2
3
4
5
6
7
8
9
10
11

class Student {
+Name string
+Courses []*Course
}
class Course {
+Name string
+Students []*Student
}
Student "1" --> "*" Course : 选课
Course "1" --> "*" Student : 学生

3 聚合关系

3.1 聚合关系的定义

聚合关系是一种特殊的关联关系,表示整体-部分的关系,其特点是:

  • 部分可以独立存在,整体并不控制部分的生命周期
  • 弱于组合,强于普通关联关系

3.2 uml表达

在UML类图中, 用带实线的空心菱形 <>-->表达聚合关系
对应的uml表达是 o-->, 菱形指向整体,箭头指向部分

3.3 聚合关系示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Player struct {
Name string
}

type Team struct {
Name string
Players []*Player // Team 聚合 Player(Players 可以独立存在)
}

func main() {
player1 := &Player{Name: "Messi"}
player2 := &Player{Name: "Ronaldo"}

team := &Team{
Name: "Dream Team",
Players: []*Player{player1, player2}, // Team 包含 Player,但 Player 不属于 Team
}

fmt.Println(team.Players[0].Name) // 输出: Messi
}

3.4 聚合关系示例类图

对应的建模表达为:

1
2
3
4
5
6
7
8
class Team {
+Name string
+Players []*Player
}
class Player {
+Name string
}
Team o--> Player : 聚合

4 组合关系

4.1 组合关系的定义

组合关系是一种强整体-部分关系,特点为:

  • 部分不能独立存在,其生命周期由整体控制
  • 比聚合关系更强,整体销毁时部分也会销毁

4.2 uml表达

在UML类图中,使用 带实线的 实心菱形 描述组合关系, 菱形指向整体
对应的uml表达为 *-->

4.3 组合关系示例代码

创建汽车时,出场必须自带发动机;报废销毁时,也必须将发动机销毁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Engine struct {
Model string
}

type Car struct {
Engine *Engine // 组合:Engine 是 Car 的不可分割部分
}

// 构造函数确保 Engine 随 Car 一起创建
func NewCar() *Car {
return &Car{
Engine: &Engine{Model: "V8"}, // Engine 的生命周期由 Car 管理
}
}

func main() {
car := NewCar()
fmt.Println(car.Engine.Model) // 输出: V8

// 当 car 被销毁时,其 Engine 也自动销毁
}

4.4 组合关系示例类图

1
2
3
4
5
6
7
class Car {
+Engine *Engine
}
class Engine {
+Model string
}
Car "1" *--> "1" Engine : 组合

5. 继承关系

5.1 继承关系定义

继承指一个类的定义可以基于另外一个已经存在的类,即子类基于父类以达到复用代码的目的
Go 语言 没有直接的类继承(Class Inheritance),但可以通过以下两种方式模拟类似功能:

  • 结构体嵌入(Embedding):实现 方法复用和字段继承(类似组合)。
  • 接口组合(Interface Composition):实现 多态和行为继承
5.2 建模表示

在UML类图中,使用带实线的三角箭头描述,箭头指向父类
在uml中用 --|>表示, A --|> B即为A继承B

5.3 继承关系示例代码

go中推崇 组合代替继承的设计模式,通常采用结构体嵌入的方式实现继承,以此访问父类属性、方法等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Animal struct {
Name string
}

func (a *Animal) Speak() string {
return "I'm an animal"
}

// Dog "继承" Animal 的字段和方法
type Dog struct {
Animal // 嵌入结构体(类似继承)
Breed string
}

func main() {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
fmt.Println(dog.Name) // 输出: Buddy(继承字段)
fmt.Println(dog.Speak()) // 输出: I'm an animal(继承方法)
}

5.4 继承关系示例类图

1
2
3
4
5
6
7
8
9

class Animal {
+Name string
+Speak() string
}
class Dog {
+Breed string
}
Dog --|> Animal : 嵌入(类似继承)

6. 实现关系

6.1 实现关系的定义

实现关系指一个类(或结构体)实现某个接口,即满足接口定义的方法契约。
Go语言中,实现关系是隐式的(无需显式声明),只要类型实现了接口的所有方法,就自动实现了该接口

6.2 uml表达

在UML类图中,以带三角箭头的虚线表示,箭头指向接口
对应的建模语言为 <|..Dog ..|> Speaker即dog实现了speaker接口

6.3 实现关系代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义接口
type Speaker interface {
Speak() string
}

// 定义结构体(实现 Speaker 接口)
type Dog struct {
Name string
}

// 实现 Speaker 接口的方法
func (d Dog) Speak() string {
return "Woof! My name is " + d.Name
}

func main() {
var speaker Speaker = Dog{Name: "Buddy"} // Dog 实现了 Speaker
fmt.Println(speaker.Speak()) // 输出: Woof! My name is Buddy
}

6.4 实现关系示例类图

1
2
3
4
5
6
7
8
interface Speaker {
+Speak() string
}
class Dog {
+Name string
+Speak() string
}
Dog ..|> Speaker : 实现

引用

1. plantuml类图