使用对象容器可更好的帮助程序管理单例的对象。将初始化好的对象放入对象容器中,使用时向容器索取而非使用new的方式主动创建,这是一种控制反转(IOC)的思想,或者说它是依赖注入(DI)。
go语言github.com/samber/do
库可以实现简单的依赖注入,且该库没有使用反射,不会有太大的性能消耗。
如下是samber/do库的一个简单的demo
package dependency_injection
import (
"fmt"
"github.com/samber/do"
"testing"
)
type A struct {
B
C
}
/*
A
/ \
B C
\
D
*/
type B struct {
Name string
}
type C struct {
Name string
D
}
type D struct {
Name string
}
func NewA(i *do.Injector) (A, error) {
return A{
B: do.MustInvoke[B](i),
C: do.MustInvoke[C](i),
}, nil
}
func NewB(i *do.Injector) (B, error) {
return B{Name: "It is B"}, nil
}
func NewC(i *do.Injector) (C, error) {
return C{
Name: "It is C",
D: do.MustInvoke[D](i),
}, nil
}
func NewD(i *do.Injector) (D, error) {
return D{Name: "It is D"}, nil
}
func TestInject(t *testing.T) {
injector := do.New()
do.Provide(injector, NewA)
do.Provide(injector, NewB)
do.Provide(injector, NewC)
do.Provide(injector, NewD)
fmt.Println(do.MustInvoke[A](injector))
fmt.Println(do.MustInvoke[B](injector))
fmt.Println(do.MustInvoke[C](injector))
fmt.Println(do.MustInvoke[D](injector))
}
该库的原理如下,可以说是一种懒加载的方式。
举个生动的例子,警察抓住了一个小偷A,这个小偷A是团伙作案,审讯的时候小偷A供出了小偷B,小偷B被捕,审讯的时候小偷B供出了小偷C,小偷D…就这样,一整个团伙都被供了出来;也就是一个对象依赖关系被树状或网状的初始化出来。
package dependency_injection
import (
"fmt"
"testing"
)
type a struct {
b
c
}
type b struct {
name string
}
type c struct {
name string
d
}
type d struct {
name string
}
func newA() (a, error) {
return a{
b: Get[b](),
c: Get[c](),
}, nil
}
func newB() (b, error) {
return b{
name: "b",
}, nil
}
func newC() (c, error) {
return c{
name: "c",
d: Get[d](),
}, nil
}
func newD() (d, error) {
return d{
name: "d",
}, nil
}
type Container[V any] map[string]V
type Callback[T any] func() (T, error)
var container = make(Container[any])
func Set[T any](c Callback[T]) {
container[getName[T]()] = c
}
func getName[T any]() string {
if res := fmt.Sprintf("%T", *new(T)); res != "<nil>" {
return res
}
return fmt.Sprintf("%T", new(T))
}
func Get[T any]() T {
callBack, ok := container[getName[T]()]
if !ok {
panic("not found")
}
t2, err := callBack.(Callback[T])()
if err != nil {
panic(err)
}
return t2
}
func TestContainer(t *testing.T) {
Set(newC)
Set(newD)
Set(newA)
Set(newB)
fmt.Println(Get[a]())
fmt.Println(Get[b]())
fmt.Println(Get[c]())
fmt.Println(Get[d]())
}
func Test(t *testing.T) {
type s struct {
a string
b string
c
}
ss := s{
a: func() string {
fmt.Println("init a")
return "a"
}(),
b: func() string {
fmt.Println("init b")
return "b"
}(),
c: func() c {
fmt.Println("init c")
return c{name: "is c", d: func() d {
fmt.Println("init d")
return d{
name: "d",
}
}()}
}(),
}
fmt.Println(ss)
}