首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >go 依赖注入实践

go 依赖注入实践

作者头像
每周聚焦
发布2025-06-26 16:25:52
发布2025-06-26 16:25:52
11900
代码可运行
举报
运行总次数:0
代码可运行

什么是依赖注入

在了解依赖注入之前,我们可以先分析下什么是依赖。

依赖

依赖的日常解释是依靠别人或事物而不能自利或自给,在软件开发中,依赖则表示的是函数,对象,模块之间的引用关系,比如函数调用,对象引用。

类似日常解释中,缺失依赖,人或事物不能自给或自利,同样,软件系统中缺失了依赖,也无法正常运行或发生正常的运行行为。

与日常依赖不同的是,软件系统的依赖,倡导的是单向依赖关系,也即A引用B,则不建议B再引用A,go语言中则直接通过不支持循环引用这一特点,迫使达到这一目标。一般有如下依赖关系:

  • 方法依赖
代码语言:javascript
代码运行次数:0
运行
复制
go 体验AI代码助手 代码解读复制代码 func A() {
     ...
  } 
     
 //B方法的依赖于A方法,才能完整运行 
 func B() { 
   ... 
   A() 
 }
  • 对象依赖
代码语言:javascript
代码运行次数:0
运行
复制
go 体验AI代码助手 代码解读复制代码type A struct{
}

// B结构依赖A结构,组成了一个完整体
type B struct {
    a A 
}

依赖关系不限于此,由此可以看出系统通过依赖运行,但因为依赖的存在,增强了模块或系统之间耦合。

降低耦合度是系统设计或代码设计中永恒的主题。

依赖注入

注入的意思,可以理解为从外部输入,而非内部产生。软件系统中,可以理解为依赖项不是由使用方(方法/对象)生成,而是从外部传入的一个过程。

依赖注入(Dependency injection, 缩写DI) 是一种软件设计模式,设计的目的是为了分离关注点,分离接收方和依赖,降低耦合度以及提升代码的重用性。优点如下:

  • 有助于代码测试
  • 提高代码的可复用性
  • 降低耦合度,有助于代码的维护

依赖注入不是编码的目标,它是为了达到松耦合的一种手段。

go实现的步骤

步骤

  1. 抽象,定义接口/类型(对于方法而言)
  2. 建立接口/方法具体实现
  3. 设计传入依赖项的方法
  4. 使用注入的依赖

方法依赖注入

假设我们要实现一个消息通知服务,可能会支持不同的发送方式,我们仅通过方法进行。

  • 常规写法
代码语言:javascript
代码运行次数:0
运行
复制
go 体验AI代码助手 代码解读复制代码
func main() {
	sendMsg("DI Test!", "sms")
}

// 使用方,根据需要根据channel值选择依赖方法,后续有新的方法需要改动,且不好测试。
func sendMsg(msg string, channel string) error {

	if channel == "email" {
		return sendByEmail(msg)
	}

	if channel == "sms" {
		return sendByEmail(msg)
	}

	return errors.New("Invalid Channel")

}

func sendByEmail(msg string) error {
	fmt.Printf("send msg by Email, msg:%s\n", msg)
	return nil
}

func sendBySms(msg string) error {
	fmt.Printf("send msg by SMS, msg:%s\n", msg)
	return nil
}

上述代码中,假设我们实现一种新的发送方式, 除了需要新增一个sendByXX方法之外,还需要修改sendMsg方法,以适配新的sendByXX方法,sendMsg与发送方式耦合过紧,得益于go中函数一等公民特性,我们可以通过定义函数类型,进行改造, 如下。

  • 依赖注入写法
代码语言:javascript
代码运行次数:0
运行
复制
go 体验AI代码助手 代码解读复制代码// 定义方法类型
type SendFunc func(msg string) error

func main() {
	sendMsg("DI Test!", sendBySms)
}

// 允许注入依赖方法
func sendMsg(msg string, sf SendFunc) error {
	return sf(msg)
}

func sendByEmail(msg string) error {
	fmt.Printf("send msg by Email, msg:%s\n", msg)
	return nil
}

func sendBySms(msg string) error {
	fmt.Printf("send msg by SMS, msg:%s\n", msg)
	return nil
}

改造后,抽象了SendFunc的类型, 新增一个sendByXX方法时,只需在调用处传入此方法即可。sendMsg中无需关注sendByXX方法的存在。测试时,只需要实现一个sendByMock方法,传入即可。

接口依赖注入

同样还是实现消息通知,采取面向对象编程方式;

  • 常规写法
代码语言:javascript
代码运行次数:0
运行
复制
go 体验AI代码助手 代码解读复制代码type Notify struct {
	channel string
}

func (n *Notify) Send(msg string) error {
	channel := n.channel
        // 依赖channel值
	if channel == "email" {
		e := new(Email)
		return e.Send(msg)
	}
	if channel == "sms" {
		sms := new(SMS)
		return sms.Send(msg)
	}
	return errors.New("Invalid Channel:" + channel)
}

func NewNotify(channel string) *Notify {
	return &Notify{
		channel,
	}
}

type Email struct {
}

func (e *Email) Send(msg string) error {
	fmt.Printf("Email Send, msg: %s \n", msg)
	return nil
}

type SMS struct {
}

func (sms *SMS) Send(msg string) error {
	fmt.Printf("SMS Send, msg: %s \n", msg)
	return nil
}

func main() {
	notify := NewNotify("email")
	notify.Send("Hello DI")
}

上述写法中,notify在调用Send方法时,需要感知发送方式,并创建对应的对象。如果新增一种通知类型,Notify的Send方法也需要修改,适配新增的类型。

  • 依赖注入
代码语言:javascript
代码运行次数:0
运行
复制
go 体验AI代码助手 代码解读复制代码// 通过接口抽象发送行为
type Sender interface {
	Send(string) error
}

type Notify struct {
	sender Sender
}

// 传入依赖项方法设计
func NewNotify(sender Sender) *Notify {
	return &Notify{
		sender,
	}
}

// 使用注入的依赖
func (notify *Notify) Send(msg string) error {
	return notify.sender.Send(msg)
}

type Email struct {
}

func (e *Email) Send(msg string) error {
	fmt.Printf("Email Send, msg: %s \n", msg)
	return nil
}

type SMS struct {
}

func (sms *SMS) Send(msg string) error {
	fmt.Printf("SMS Send, msg: %s \n", msg)
	return nil
}

func main() {
    // 发送msg的依赖,由调入方传入,后续如果新增一种发送方式,sendMsg方法无需修改
    notify := NewNotify(&Email{})
    notify.Send("Hello DI")
}

改造后,我们抽象了Sender接口,新增一种发送方式时,只需要实现Sender接口,并在NewNotify时,传入即可。Notify不需要感知新增发送方式的存在,解耦了Notify与新增的发送方式。

总结

依赖注入(DI)是一种降低代码耦合度的实现方式,具有比较实用的模板或实现步骤,我们在运用的过程,只需要把握依赖尽量不要由接收者生成,而是通过外部注入这一原则,就能比较容易实现依赖注入,写出松耦合的代码。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是依赖注入
    • 依赖
    • 依赖注入
  • go实现的步骤
    • 步骤
    • 方法依赖注入
    • 接口依赖注入
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档