Wire 并不会像 Java 的 Spring 一样,能自动处理循环依赖,Go 语言的设计哲学如此,不合理的特性,它就是不会支持。但开发业务时,难免有在同一层有循环依赖的情况。
方法一:将 func 传进去
在调用某方法时,直接将func传进去
go
// a.go
type A struct {
}
// demo 方法
func (a *A) demo(bFn func()) {
// 假设这里依赖B的方法
// 则直接由上一层将需要的方法传进来
bFn()
}
// b.go
type B struct {
}
// demo 方法
func (b *B) demo() {
// ...
}
方法二:依赖倒置
如果A依赖B,则在A中声明interface,在外层用 wire.Bind 将A中的interface与B的实现进行绑定
go
// a.go
type A struct {
// 直接写依赖
b BIf
}
// 声明依赖的interface
type BIf interface {
demo() int
}
// NewA 构造方法
func NewA(b BIf) *A {
// 直接依赖B的interface
return &A{b: b}
}
// demo 方法
func (a *A) demo() {
// 调用依赖的demo
a.b.demo()
}
// b.go
type B struct {
}
func NewB() *B {
return &B{}
}
// demo 方法
func (b *B) demo() {
// ...
}
// build.go
// 指定将B实例绑定到BIf上
wire.Build(
NewA,
NewB,
// 将B实例绑定到BIf上
wire.Bind(new(BIf), new(*B)), // wire.Bind 的第二个参数是类型,这里用 new(*B) 表示 *B 类型,并不是实例
))
方法三:使用Set*来实现
go
// a.go
type A struct {
// 依赖*B
b *B
}
// NewA 构造方法
func NewA() *A {
// 这里依赖的不能直接设置进去,暴露下面的SetA来
return &A{}
}
// SetB 设置a依赖
func (a *A) SetB(b *B) {
a.b = b
}
// demo 方法
func (a *A) demo() {
// 调用依赖的demo
a.b.demo()
}
// b.go
type B struct {
// 依赖*A
a *A
}
func NewB() *B {
return &B{}
}
// SetA 设置A依赖
func (b *B) SetA(a *A) {
b.a = a
}
// demo 方法
func (b *B) demo() {
// ...
}
// inject_dependencies.go
// InjectDependencies 是一个无业务含义的类型,仅用于在 wire 中触发 Set* 注入。
// 注意:它必须被其他 provider 引用(例如作为根组件的依赖,依赖一下不做任何调用),否则 wire 会报 unused provider 错误。
type InjectDependencies struct {
}
func NewInjectDependencies(a *A, b *B) *InjectDependencies {
a.SetB(b) // 注入
b.SetA(a) // 注入
return &InjectDependencies{}
}
// build.go
wire.Build(
NewA,
NewB,
// 提供依赖注入,用它来统一做各实例的Set*
NewInjectDependencies,
)
对比
| 方案 | 推荐 | 原因 |
|---|---|---|
| 将 func 传进去 | ⭐️⭐️⭐️ | 如果一个实例多个方法都依赖,则每个方法都需要传,且阅读和跟踪不友好 |
| 依赖倒置 | ⭐️⭐️⭐️⭐️ | 工程思想上更合理,但同样存在阅读和跟踪代码困难问题 |
| 使用Set*来实现 | ⭐️⭐️⭐️⭐️⭐️ | 阅读和跟踪代码非常友好 |