Wire 依赖注入
# Wire 依赖注入
# 基本概念
依赖注入(Dependency Injection,简称DI),是控制反转(Inversion of Control,简称IoC)思想的一种实现,主要解决面向对象编程中存在的耦合问题。
如图1所示,Object ABCD之间相互依赖,当修改其中一项时就需要改动其它被依赖项,改动就会十分麻烦。当引入第三方IoC容器后,我们就不需要关注 Object ABCD 间的依赖关系,当需要某个对象时,直接使用即可。
- 依赖注入,可以理解为就是在解耦
- 控制反转、依赖注入实际上是对同一件事情的不同描述(见示例1)
# 示例辨析
# 示例1 《根据不同数据来源获取用户名称》
# 1.1 耦合写法
- 定义一个拥有 name 属性的 User 结构体
- 定义多个数据来源,用于为 User.name 赋值
- 定义构造函数,在构造时写入 name 属性
package main
import "fmt"
// User 定义 User 结构体,拥有 Name 私有属性
type User struct {
Name string
}
// getUserNameByStr 获取固定名称
func getUserNameByStr() string {
return "小张"
}
// getUserNameByMysql 从mysql获取名称
func getUserNameByMysql() string {
// TBD
return "小张-mysql"
}
// getUserNameByOracle 从Oracle获取名称
func getUserNameByOracle() string {
// TBD
return "小张-Oracle"
}
func NewUser() *User {
// 初始化时需要传入Name属性,Name来源有可以有多个
return &User{Name: getUserNameByStr()}
}
func (u User) MyName() {
fmt.Println(u.Name)
}
func main() {
// 每次变更数据来源,需要修改源代码,控制权在程序员
user := NewUser()
user.MyName() // "小张"
}
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
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
注:
1. name 属性在构造时,已经写死来源,如遇到需求变更,需要改源代码
2. 修改代码的话,控制权即在程序员手里
# 1.2 依赖注入
在1.1的基础上
- 改造构造函数,name赋值依赖于外部传入
- 定义chioceFrom 函数便于理解控制反转
package main
import (
"fmt"
"strings"
)
// User 定义 User 结构体,拥有 Name 私有属性
type User struct {
Name string
}
// getUserNameByStr 获取固定名称
func getUserNameByStr() string {
return "小张"
}
// getUserNameByMysql 从mysql获取名称
func getUserNameByMysql() string {
// TBD
return "小张-mysql"
}
// getUserNameByOracle 从Oracle获取名称
func getUserNameByOracle() string {
// TBD
return "小张-Oracle"
}
func NewUser(name string) *User {
// 改造构造函数,name 属性从原有的自己创建,变为外部传入,不再关心name怎么来的
return &User{Name: name}
}
func (u User) MyName() {
fmt.Println(u.Name)
}
// choiceFrom 根据用户输入的关键字,指定来源。 (便于理解控制反转封装的函数,实际依赖注入的思想体现在改造构造函数)
func choiceFrom(data string) string {
if strings.EqualFold("MYSQL", data) {
return getUserNameByMysql()
} else if strings.EqualFold("ORACLE", data) {
return getUserNameByOracle()
}
return getUserNameByStr()
}
func main() {
// 解耦后,name可以交由用户传入选择,控制权转为用户端
//name := getUserNameByMysql()
var input string
fmt.Println("请输入您指定的数据来源:")
fmt.Scanln(&input) // "mysql"
name := choiceFrom(input)
user := NewUser(name)
user.MyName() // "小张-mysql"
}
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
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
注:
- 构造函数改造以后,就无需关心name是怎么产生的,只需要在new的时候注入name属性即可。这就是我们说的依赖注入。
- 而对于name属性的产生从原来的程序员改代码,变更成现在的用户输入灵活选择,形成了控制反转。
# 示例2 《生产关系》
注意
- 原始社会里,没有社会分工。须要斧子的人(调用者)仅仅能自己去磨一把斧子(被调用者)。相应的情形为: 软件程序里的调用者自己创建被调用者。
- 进入工业社会,工厂出现。斧子不再由普通人完毕,而在工厂里被生产出来,此时须要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。相应软件程序的简单工厂的设计模式。
- 进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令: 须要斧子。斧子就自然出如今他面前。依赖注入。
从 1、2 可以看出:
- 不同点,是解放了自己工作,不需要去磨这个斧子了
- 相同点,就是这个主导者,还依然是自己。就说你自己磨斧子还是自己去工厂里去取。这个主要调用者当事人还是自己。
从 2、3 可以看出:
- 相同点,我们不需要去磨这个斧子了。
- 不同点,就是主导者变了,斧子已经不需要我们去取了,而是我们想要什么就去要什么。就是控制权已经改变了,他已经不是我们自己去控制的了。
# 示例3 业务代码
# 3.1 依赖注入
该示例模拟了业务流程,定义 应用层(Application)、领域层(Domain)、基础设施层(Infrastructure)相关代码,展示不存wire托管的情况下,应该如何处理依赖。
package main
import "fmt"
// User Infrastructure 层结构体,模拟用户对象,拥有私有属性Name
type User struct {
Name string
}
// Mysql Infrastructure 层 数据源
type Mysql struct {
Host string
Port int
Username string
Password string
}
// NewMysql Mysql 构造函数
func NewMysql() *Mysql {
return &Mysql{Host: "127.0.0.1", Port: 3306, Username: "root", Password: "123456"}
}
// getUserInfoByMysql 模拟数据源
func (mysql *Mysql) getUserInfoByMysql(user User) *User {
// 模拟从 Mysql 中取数据
user = User{Name: "XiaoZhang-mysql"}
return &user
}
// Data Infrastructure 层结构体
type Data struct {
Mysql *Mysql
}
// NewData Data 数据源构造函数
func NewData(mysql *Mysql) *Data {
return &Data{Mysql: mysql}
}
// UserRepo Infrastructure 层 repo接口,抽象相关方法,无需关心相关实现
type UserRepo interface {
getUserInfo() *User
}
// UserRepoImp repo实现类
type UserRepoImp struct {
data *Data
}
func NewUserRepoImp(data *Data) UserRepo {
return &UserRepoImp{data: data}
}
// getUserInfo 实现 UserRepo 接口 getUserInfo 抽象方法
func (r *UserRepoImp) getUserInfo() *User {
user := r.data.Mysql.getUserInfoByMysql(User{})
return user
}
// UserCase Domain 层结构体
type UserCase struct {
UserRepo UserRepo
}
// NewUserCase UserCase 构造
func NewUserCase(repo UserRepo) *UserCase {
return &UserCase{UserRepo: repo}
}
// getUserInfo 定义业务逻辑函数 getUserInfo
func (uc *UserCase) getUserInfo() *User {
// TBD
return uc.UserRepo.getUserInfo()
}
// Service Application 层结构体
type Service struct {
UserCase *UserCase
}
// NewService Service 构造函数
func NewService(uc *UserCase) *Service {
return &Service{UserCase: uc}
}
// getUserInfo 在 Service 中实现 getUserInfo 方法(该方法抽象在API层,即grpc生成的代码中,这里未体现)
func (s *Service) getUserInfo() *User {
return s.UserCase.getUserInfo()
}
func main() {
mysql := NewMysql()
data := NewData(mysql)
userRepo := NewUserRepoImp(data)
uc := NewUserCase(userRepo)
service := NewService(uc)
user := service.getUserInfo()
fmt.Println(*user)
}
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
注:
1. 当前案例在case和repo 之前的实现违反了DIP(依赖倒置)原则。Go的最佳实践中,更推荐返回具体实现而不是接口。wire 无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。通过 wire.NewSet 和 wire.Bind 进行绑定。(例中未体现,具体可以看相关链接的文章)
# 3.2 Wire
引入wire实现依赖注入
- 编写 wire.go 文件
- 通过 wire 生成 wire_gen.go 文件
- 改写代码
//go:build wireinject
// +build wireinject
// wire.go
package main
import (
"github.com/google/wire"
)
func InitializeService() *Service {
wire.Build(NewService, NewUserCase, NewUserRepoImp, NewData, NewMysql)
return &Service{}
}
// 简写方法
// func InitializeService() *Service {
// panic(wire.Build(NewService, NewUserCase, NewUserRepoImp, NewData, NewMysql))
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
service := InitializeService()
user := service.getUserInfo()
fmt.Println(*user)
}
1
2
3
4
5
2
3
4
5
# 相关文章
wire package - github.com/google/wire - Go Packages (opens new window)
go语言依赖注入指的是什么 - 编程语言 - 亿速云 (opens new window)
Spring之IOC - shawWey - 博客园 (opens new window)
编辑 (opens new window)
上次更新: 2024/04/27, 16:25:38