Dreamer Dreamer
首页
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

lycpan233

白日梦想家
首页
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Mysql

  • Node

  • Go

    • Wire 依赖注入
      • Wire 依赖注入
        • 基本概念
        • 示例辨析
        • 示例1 《根据不同数据来源获取用户名称》
        • 1.1 耦合写法
        • 1.2 依赖注入
        • 示例2 《生产关系》
        • 示例3 业务代码
        • 3.1 依赖注入
        • 3.2 Wire
        • 相关文章
  • Docker

  • 后端
  • Go
lycpan233
2024-04-27
目录

Wire 依赖注入

# Wire 依赖注入

# 基本概念

依赖注入(Dependency Injection,简称DI),是控制反转(Inversion of Control,简称IoC)思想的一种实现,主要解决面向对象编程中存在的耦合问题。

IoC

图1

如图1所示,Object ABCD之间相互依赖,当修改其中一项时就需要改动其它被依赖项,改动就会十分麻烦。当引入第三方IoC容器后,我们就不需要关注 Object ABCD 间的依赖关系,当需要某个对象时,直接使用即可。

  1. 依赖注入,可以理解为就是在解耦
  2. 控制反转、依赖注入实际上是对同一件事情的不同描述(见示例1)

DI_chat_1714205651.png


# 示例辨析

# 示例1 《根据不同数据来源获取用户名称》

# 1.1 耦合写法
  1. 定义一个拥有 name 属性的 User 结构体
  2. 定义多个数据来源,用于为 User.name 赋值
  3. 定义构造函数,在构造时写入 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

注:

1. name 属性在构造时,已经写死来源,如遇到需求变更,需要改源代码

2. 修改代码的话,控制权即在程序员手里


# 1.2 依赖注入

在1.1的基础上

  1. 改造构造函数,name赋值依赖于外部传入
  2. 定义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

注:

  1. 构造函数改造以后,就无需关心name是怎么产生的,只需要在new的时候注入name属性即可。这就是我们说的依赖注入。
  2. 而对于name属性的产生从原来的程序员改代码,变更成现在的用户输入灵活选择,形成了控制反转。

# 示例2 《生产关系》

注意

  1. 原始社会里,没有社会分工。须要斧子的人(调用者)仅仅能自己去磨一把斧子(被调用者)。相应的情形为: 软件程序里的调用者自己创建被调用者。
  2. 进入工业社会,工厂出现。斧子不再由普通人完毕,而在工厂里被生产出来,此时须要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。相应软件程序的简单工厂的设计模式。
  3. 进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令: 须要斧子。斧子就自然出如今他面前。依赖注入。

从 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

注:

1. 当前案例在case和repo 之前的实现违反了DIP(依赖倒置)原则。Go的最佳实践中,更推荐返回具体实现而不是接口。wire 无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。通过 wire.NewSet 和 wire.Bind 进行绑定。(例中未体现,具体可以看相关链接的文章)

# 3.2 Wire

引入wire实现依赖注入

  1. 编写 wire.go 文件
  2. 通过 wire 生成 wire_gen.go 文件
  3. 改写代码
//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
func main() {
	service := InitializeService()
	user := service.getUserInfo()
	fmt.Println(*user)
}
1
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)

Golang依赖注入框架wire全攻略 - 掘金 (opens new window)

编辑 (opens new window)
上次更新: 2025/06/12, 08:45:25
package.json 中波浪号~ 异或号^ 是什么意思?
docker基础概念

← package.json 中波浪号~ 异或号^ 是什么意思? docker基础概念→

最近更新
01
docker基础概念
02-26
02
js 获取变量准确类型
02-19
03
Mysql SQL 优化思路
02-18
更多文章>
Theme by Vdoing | Copyright © 2023-2025 Dreamer | MIT License
粤ICP备2025379918号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式