前言
学习过Java 开发的同学,一定对依赖注入有所耳闻。依赖注入(Dependency Injection,简称 DI)是一种设计模式,通过将组件依赖项外部化,使代码更加模块化和易于测试。对于Wire
的使用,可以使得开发人员不用在手写对应实例的创建,只需要规范化的编写对应的依赖代码,并通过wire gen 对应目录
即可生成对应的实例创建代码。
一、什么是依赖注入
依赖注入是一种设计模式,用于将对象的依赖关系通过构造函数、方法或属性注入,而不是在对象内部创建依赖。这样可以提高代码的可测试性和灵活性。
1 举个栗子
比如在Web
开发中,常用的一些设计模式,都会将业务分层多个层去处理,比如MVC
结构,在Controller
层就要调用Service
层的业务,而Service
层又需要dao
。这种存在依赖的关系,在创建实例时,不是在内部创建,而是通过注入就是依赖注入。
package main
import "fmt"
type Dao struct {
}
func NewDao() *Dao {
return &Dao{}
}
func (d *Dao) Do() {
fmt.Println("do dao")
}
// 实现接口的结构体
type Service struct {
dao *Dao
}
func NewService(dao *Dao) *Service {
return &Service{
dao: dao,
}
}
func (s *Service) Execute() {
fmt.Println("Executing service...")
s.dao.Do()
}
// 使用依赖注入的结构体
type Controller struct {
service *Service
}
func NewController(s *Service) *Controller {
return &Controller{service: s}
}
func (c *Controller) Run() {
c.service.Execute()
}
func main() {
daoDao := dao.NewDao()
serviceService := service.NewService(daoDao)
controllerController := controller.NewController(serviceService)
controller.Run()
}
如上述代码中的main
函数中的内容就是依赖注入的形式
dao := NewDao()
service := NewService(dao)
// service 通过传入的方式进入到Controller中,而不是内部创建
controller := NewController(service)
controller.Run()
2. 依赖注入的好处
依赖注入的好处包括:
- 可测试性:可以轻松替换依赖,进行单元测试。
- 解耦合:减少模块之间的直接依赖,增强代码灵活性。
- 易于维护:更改依赖时无需修改使用它的代码。
- 可复用性:相同的依赖可以在多个地方使用,提高代码复用性。
- 更清晰的代码结构:依赖注入有助于明确对象之间的关系,使得代码结构更加清晰,易于理解。
二、Wire
Wire
是一个专为依赖注入(Dependency Injection)
设计的代码生成工具,它可以自动生成用于初始化各种依赖关系的代码,从而帮助我们更轻松地管理和注入依赖关系。
1.使用wire的前置条件
使用wire需要两个前置条件:
- wire命令安装
- golang中使用 wire 提供者生成函数
安装wire命令
go install github.com/google/wire/cmd/wire@latest
代码导入wire
使用 Go 的包管理工具安装 Wire:
go get github.com/google/wire
创建Wire生成函数
// +build wireinject
package main
import "github.com/google/wire"
func InitializeController() *Controller {
wire.Build(NewMyService, NewController)
return &Controller{}
}
2.小试牛刀
刚才main中的代码,分别拆分出来,这样更符合实际的开发场景。
├─controller
├─dao
├─service
└─wiretest
每个部分的代码
controller/controller.go
package controller
import "wirelearn/service"
// 使用依赖注入的结构体
type Controller struct {
service *service.Service
}
func NewController(s *service.Service) *Controller {
return &Controller{service: s}
}
func (c *Controller) Run() {
c.service.Execute()
}
dao/dao.go
package dao
import "fmt"
type Dao struct {
}
func NewDao() *Dao {
return &Dao{}
}
func (d *Dao) Do() {
fmt.Println("do dao")
}
service/service.go
package service
import (
"fmt"
"wirelearn/dao"
)
// 实现接口的结构体
type Service struct {
dao *dao.Dao
}
func NewService(dao *dao.Dao) *Service {
return &Service{
dao: dao,
}
}
func (s *Service) Execute() {
fmt.Println("Executing service...")
s.dao.Do()
}
wiretest/wire.go
//go:build wireinject
// +build wireinject
package wiretest
import (
"github.com/google/wire"
"wire-learn/wirelearn/dao"
"wire-learn/wirelearn/controller"
"wire-learn/wirelearn/service"
)
var providerSet = wire.NewSet(
controller.NewController,
service.NewService,
dao.NewDao,
)
func Initialize() (*controller.Controller, error) {
panic(wire.Build(providerSet))
}
在wire.go
文件中,我们只需要把生成实例的方法指针放到wire.NewSet
中,就完成主要作用wire的编写,Initialize
方法的返回就是我们要给的最终实例
main.go
func main() {
controller, err := wiretest.Initialize()
if err != nil {
panic(err)
}
controller.Run()
}
在main
中,我们只需要调用wiretest.Initialize
中的方法获取对应的实例。这块就是wire
自动生成的代码,通常和wire.go
在同一个文件夹中
看下我们生成的wire_gen.go
的代码吧
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wiretest
import (
"github.com/google/wire"
"wirelearn/controller"
"wirelearn/dao"
"wirelearn/service"
)
// Injectors from wire.go:
func Initialize() (*controller.Controller, error) {
daoDao := dao.NewDao()
serviceService := service.NewService(daoDao)
controllerController := controller.NewController(serviceService)
return controllerController, nil
}
// wire.go:
var providerSet = wire.NewSet(controller.NewController, service.NewService, dao.NewDao)
可以看到Initialize
方法中的代码和我们没使用wire
之前,在main方法中写的,基本相同。所以我们也可以大致了解wire
在实际任务中实际工作方式。
总结
本文通过示例讲解了依赖注入的原理及其在 Go 语言中的实际应用,尤其是利用 Wire 工具自动生成依赖代码的过程。通过依赖注入,代码更加模块化、可测试,并减少了各模块之间的耦合,增强了可维护性。 Wire 工具进一步简化了这个过程,避免了繁琐的手动编写依赖初始化代码。
本文是经过个人查阅相关资料后理解的提炼,可能存在理论上理解偏差的问题,如果您在阅读过程中发现任何问题或有任何疑问,请不吝指出,我将非常感激并乐意与您讨论。谢谢您的阅读!