琢磨设计模式与抽象,可以说是我的最爱之一了。刚学 Go 的时候,我就陶醉于其的 interface
设计。
这次,我们来聊聊 Go 语言的依赖注入(DI)库 samber/do 。
本文不是一行行分析源码,而是尝试一步步复现作者的设计思路。
挖个坑先(为什么只讲 samber/do) {#挖个坑先为什么只讲-samberdo}
1. IoC 与 DI {#1-ioc-与-di}
首先,澄清一下,控制反转(IoC)与依赖注入(DI)是两个不同的概念。
(弄不清没关系,可以看这篇文章: 还没写 )
简单来说,控制反转是一种编程思想,而依赖注入是一种实现控制反转的方式。
2. 广义 IoC、DI,以及 DI Framework,以及 Go 的设计哲学 {#2-广义-iocdi以及-di-framework以及-go-的设计哲学}
其次,似乎一提到 IoC、DI 就必须提到 Spring,甚至很多人不用 Spring 举例子,就讲不清这两个概念。
换言之,很多人都弄不清,DI 与 DI Framework 的区别。
这又牵扯到另外一个问题, Go 是否需要 IoC, DI,还是说有着更适合 Go 的依赖倒置设计哲学?
3. DI 框架的众多实现 {#3-di-框架的众多实现}
且不说 DI 只是这些概念的一小部分,连 Go 的 DI 框架都有不少,例如:
总而言之
- 因为一、二两点,导致在这之前,我应该写一篇只涉及编程思想的「论道」的文章。先欠着,由于篇幅和暂时的能力原因。
- 因为第三点,我需要对比不同的 Go 语言的 DI 框架。
- 所以,这篇文章,我只能先分析分析 samber/do 本身。
复现 samber/do 之路 {#复现-samberdo-之路}
先给 DI 来个狭义的定义吧:
狭义DI定义
对象的使用方式不应该依赖于对象的创建方式。
所以我们要实现的,就是:
- 提供一个「第三方」
- 对象创建者,把特定类型的对象创建出来并注册到第三方
- 对象使用者,从第三方获取对象
1. 需要知道的前置知识 {#1-需要知道的前置知识}
为了看懂本文,你需要知道:
- Go 的基础语法
interface
与 泛型
2. 最简单的版本 {#2-最简单的版本}
原理也很简单,我们只要维护一个 map[reflect.Type]reflect.Value
,创建方(或者说 Provider
)把对象创建出来,放到 map
里,使用方(调用方)从 map
里取出按类型取出对象即可。
Go 从 1.18 起,支持泛型,所以我们可以用泛型来简化代码。使用 map[string]any
来代替 map[reflect.Type]reflect.Value
,将类型名作为 key
,对象作为 value
。
代码如下,忽略了错误处理和并发安全:
package
main
import
(
"fmt"
"reflect"
)
type
Injector
struct
{
services
map
[
string
]
any
s2
map
[
reflect
.
Type
]
reflect
.
Value
}
func
New
()
*
Injector
{
return
&
Injector
{
services
:
make
(
map
[
string
]
any
),
}
}
func
ProvideValue
[
T
any
](
i
*
Injector
,
v
T
)
{
// ignore error handling
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="nx">
i
</span>
<span class="p">
.
</span>
<span class="nx">
services
</span>
<span class="p">
[
</span>
<span class="nx">
generateServiceName
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
]()]
</span>
<span class="p">
=
</span>
<span class="nx">
v
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="nx">
Invoke
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="nx">
any
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="o">
*
</span>
<span class="nx">
Injector
</span>
<span class="p">
)
</span>
<span class="nx">
T
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// ignore error handling
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="k">
return
</span>
<span class="nx">
i
</span>
<span class="p">
.
</span>
<span class="nx">
services
</span>
<span class="p">
[
</span>
<span class="nx">
generateServiceName
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
]()].(
</span>
<span class="nx">
T
</span>
<span class="p">
)
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="nx">
generateServiceName
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="nx">
any
</span>
<span class="p">
]()
</span>
<span class="kt">
string
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
var
</span>
<span class="nx">
t
</span>
<span class="nx">
T
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// struct
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="nx">
name
</span>
<span class="o">
:=
</span>
<span class="nx">
fmt
</span>
<span class="p">
.
</span>
<span class="nf">
Sprintf
</span>
<span class="p">
(
</span>
<span class="s">
"%T"
</span>
<span class="p">
,
</span>
<span class="nx">
t
</span>
<span class="p">
)
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
if
</span>
<span class="nx">
name
</span>
<span class="o">
!=
</span>
<span class="s">
"<nil>"
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
name
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// interface
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="k">
return
</span>
<span class="nx">
fmt
</span>
<span class="p">
.
</span>
<span class="nf">
Sprintf
</span>
<span class="p">
(
</span>
<span class="s">
"%T"
</span>
<span class="p">
,
</span>
<span class="nb">
new
</span>
<span class="p">
(
</span>
<span class="nx">
T
</span>
<span class="p">
))
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="nf">
main
</span>
<span class="p">
()
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
i
</span>
<span class="o">
:=
</span>
<span class="nf">
New
</span>
<span class="p">
()
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// pkg1
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="kd">
type
</span>
<span class="nx">
msg
</span>
<span class="kt">
string
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
ProvideValue
</span>
<span class="p">
[
</span>
<span class="nx">
msg
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
,
</span>
<span class="nf">
msg
</span>
<span class="p">
(
</span>
<span class="s">
"Hello World"
</span>
<span class="p">
))
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// maybe in pkg2
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="nx">
fmt
</span>
<span class="p">
.
</span>
<span class="nf">
Println
</span>
<span class="p">
(
</span>
<span class="nx">
Invoke
</span>
<span class="p">
[
</span>
<span class="nx">
msg
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
))
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// Output: Hello World
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="p">
}
</span>
</span>
</span>
</code>
</pre>
可以看到,在
main
函数中,
- 通过
ProvideValue
将类型为msg
的对象注册到Injector
中, - 使用的时候(可能在其他
pkg
中),通过Invoke
来获取特定类型的对象。
值得一提的是,这里注册的对象可以是任何类型,包括函数、接口等。
3. 突破单例 {#3-突破单例}
如果我们使用 map[reflect.Type]reflect.Value
来存储对象,那么每个类型的对象都只能有一个实例。
这也是我们使用 map[string]any
的原因,将类型名作为默认的 key
,同时用户也可以自定义 key
。
下面我们增加两个函数,分别是注入自定义 name
的对象,以及通过自定义 name
获取对象。
代码如下(补全了错误处理,但仍然忽略了并发安全):
func
ProvideNamedValue
[
T
any
](
i
*
Injector
,
name
string
,
v
T
)
{
_
,
ok
:=
i
.
services
[
name
]
if
ok
{
panic
(
fmt
.
Errorf
(
"DI: service `%s` has already been declared"
,
name
))
}
i
.
services
[
name
]
=
v
}
func
InvokeNamed
[
T
any
](
i
*
Injector
,
name
string
)
(
t
T
,
err
error
)
{
serviceAny
,
ok
:=
i
.
services
[
name
]
if
!
ok
{
return
t
,
fmt
.
Errorf
(
"DI: could not find service `%s`"
,
name
)
}
serviceT
,
ok
:=
serviceAny
.(
T
)
if
!
ok
{
return
t
,
fmt
.
Errorf
(
"DI: service `%s` is not of type `%T`"
,
name
,
t
)
}
return
serviceT
,
nil
}
4. 对象调用链(懒加载) {#4-对象调用链懒加载}
上面的代码,我们能够一次注入一种类型的对象,但涉及到对象间的依赖时,我们需要按照依赖顺序,手动注入对象。
type
Wheel
struct
{}
type
Engine
struct
{}
type
Car
struct
{
Engine
*
Engine
Wheels
[]
*
Wheel
}
以目前的实现,我们必须先注入 Engine
,再注入 Wheel
,最后注入 Car
。
代码大概是这样:
对象创建方(忽略错误处理):
engine
:=
&
Engine
{}
ProvideValue
[
*
Engine
](
i
,
engine
)
wheel0
,
wheel1
,
weel2
,
wheel3
:=
&
Wheel
{},
&
Wheel
{},
&
Wheel
{},
&
Wheel
{}
ProvideNamedValue
[
*
Wheel
](
i
,
"wheel0"
,
wheel0
)
ProvideNamedValue
[
*
Wheel
](
i
,
"wheel1"
,
wheel1
)
ProvideNamedValue
[
*
Wheel
](
i
,
"wheel2"
,
weel2
)
ProvideNamedValue
[
*
Wheel
](
i
,
"wheel3"
,
wheel3
)
car
:=
&
Car
{
Engine
:
Invoke
[
*
Engine
](
i
),
Wheels
:
[]
*
Wheel
{
// ignore error return
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="nx">
InvokeNamed
</span>
<span class="p">
[
</span>
<span class="o">
*
</span>
<span class="nx">
Wheel
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
,
</span>
<span class="s">
"wheel0"
</span>
<span class="p">
),
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
InvokeNamed
</span>
<span class="p">
[
</span>
<span class="o">
*
</span>
<span class="nx">
Wheel
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
,
</span>
<span class="s">
"wheel1"
</span>
<span class="p">
),
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
InvokeNamed
</span>
<span class="p">
[
</span>
<span class="o">
*
</span>
<span class="nx">
Wheel
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
,
</span>
<span class="s">
"wheel2"
</span>
<span class="p">
),
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
InvokeNamed
</span>
<span class="p">
[
</span>
<span class="o">
*
</span>
<span class="nx">
Wheel
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
,
</span>
<span class="s">
"wheel3"
</span>
<span class="p">
),
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
},
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
ProvideValue
</span>
<span class="p">
[
</span>
<span class="o">
*
</span>
<span class="nx">
Car
</span>
<span class="p">
](
</span>
<span class="nx">
i
</span>
<span class="p">
,
</span>
<span class="nx">
car
</span>
<span class="p">
)
</span>
</span>
</span>
</code>
</pre>
对象使用方:
c
:=
Invoke
[
Car
](
i
)
当创建 Car 时,我们必须先创建 Engine 和 Wheel,然后再创建 Car。否则在创建 Car 时,会因为无法注入 Engine 和 Wheel 而报错。
所以本质上,我们只实现了「单层」的依赖注入,只有最底层的对象,才能不用关心依赖到底是谁创建的;高层的对象,依然要注意依赖的对象是否已经创建。
而且,在实际使用过程中,Car, Engine, Wheel 这些对象,可能是在不同的 pkg
中创建的,初始化的关系是随机的(或者我们并不想关心初始化的顺序,我们只想在用的时候,能够拿到对象)。
怎么才能实现,无论依赖有多少级,都能够自动注入呢?
关键
其实问题的关键在于,在调用 ProvideVaue
时,需要传入一个确定的值,但这个值其实可以在最后要使用的时候,才去确定。 答案呼之欲出:懒加载。
代码可以这样改:
type
Provider
[
T
any
]
func
(
*
Injector
)
(
T
,
error
)
type
lazyService
[
T
any
]
struct
{
provider
Provider
[
T
]
}
func
Provide
[
T
any
](
i
*
Injector
,
provider
Provider
[
T
])
{
name
:=
generateServiceName
[
T
]()
ProvideNamed
[
T
](
i
,
name
,
provider
)
}
func
ProvideNamed
[
T
any
](
i
*
Injector
,
name
string
,
provider
Provider
[
T
])
{
_
,
ok
:=
i
.
services
[
name
]
if
ok
{
panic
(
fmt
.
Errorf
(
"DI: service `%s` has already been declared"
,
name
))
}
i
.
services
[
name
]
=
lazyService
[
T
]{
provider
:
provider
}
}
其实这有点工厂模式的味道了。
我们定义了一个 Provider
类型,由用户传入,仅仅在使用的时候,才去调用 Provider
,一个个去分析依赖,然后创建对象。
现在,我们完全可以先 Provide
一个 Car
,再去 Provide
Engine
和 Wheel
,而不用关心它们的创建顺序。(只要保证,在使用前,所有的依赖都已经被 Provide
过了)
5. 重构代码 {#5-重构代码}
看到这里,读者可能已经发现了不对!
懒加载和前面的代码,services 的类型不一样! 这会导致我们在 Invoke
时,需要分情况处理。
解决方法同样很简单: 接口 !
type
Service
[
T
any
]
interface
{
getName
()
string
getInstance
(
*
Injector
)
(
T
,
error
)
}
最前面提到的,直接传入值进行 Provide 的 service,我们可以称之为「饿汉service」,实现方法可以说是一览无余:
type
Service
[
T
any
]
interface
{
getName
()
string
getInstance
(
*
Injector
)
(
T
,
error
)
}
type
eagerService
[
T
any
]
struct
{
name
string
instance
T
}
// func newEagerService ...
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="p">
(
</span>
<span class="nx">
s
</span>
<span class="o">
*
</span>
<span class="nx">
eagerService
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
])
</span>
<span class="nf">
getName
</span>
<span class="p">
()
</span>
<span class="kt">
string
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
name
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="p">
(
</span>
<span class="nx">
s
</span>
<span class="o">
*
</span>
<span class="nx">
eagerService
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
])
</span>
<span class="nf">
getInstance
</span>
<span class="p">
(
</span>
<span class="nx">
i
</span>
<span class="o">
*
</span>
<span class="nx">
Injector
</span>
<span class="p">
)
</span>
<span class="p">
(
</span>
<span class="nx">
T
</span>
<span class="p">
,
</span>
<span class="kt">
error
</span>
<span class="p">
)
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
instance
</span>
<span class="p">
,
</span>
<span class="kc">
nil
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
</code>
</pre>
对于懒加载,我们可以称之为「懒汉service」,实现方法如下:
type
lazyService
[
T
any
]
struct
{
name
string
provider
Provider
[
T
]
instance
T
built
bool
}
// func newLazyService ...
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="p">
(
</span>
<span class="nx">
s
</span>
<span class="o">
*
</span>
<span class="nx">
lazyService
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
])
</span>
<span class="nf">
getName
</span>
<span class="p">
()
</span>
<span class="kt">
string
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
name
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="kd">
func
</span>
<span class="p">
(
</span>
<span class="nx">
s
</span>
<span class="o">
*
</span>
<span class="nx">
lazyService
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
])
</span>
<span class="nf">
getInstance
</span>
<span class="p">
(
</span>
<span class="nx">
i
</span>
<span class="o">
*
</span>
<span class="nx">
Injector
</span>
<span class="p">
)
</span>
<span class="p">
(
</span>
<span class="nx">
t
</span>
<span class="nx">
T
</span>
<span class="p">
,
</span>
<span class="nx">
err
</span>
<span class="kt">
error
</span>
<span class="p">
)
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
if
</span>
<span class="p">
!
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
built
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
// use provider to build instance
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
instance
</span>
<span class="p">
,
</span>
<span class="nx">
err
</span>
<span class="p">
=
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nf">
provider
</span>
<span class="p">
(
</span>
<span class="nx">
i
</span>
<span class="p">
)
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
if
</span>
<span class="nx">
err
</span>
<span class="o">
!=
</span>
<span class="kc">
nil
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
t
</span>
<span class="p">
,
</span>
<span class="nx">
err
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
built
</span>
<span class="p">
=
</span>
<span class="kc">
true
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
s
</span>
<span class="p">
.
</span>
<span class="nx">
instance
</span>
<span class="p">
,
</span>
<span class="kc">
nil
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
</code>
</pre>
代码中忽略了错误处理与并发。
现在,我们只要往 map[string]any
中存入 Service
,到时候,无论取出的是哪种实现,都可以调用 getInstance
方法,获取到实例。
6. 生命周期 {#6-生命周期}
这个 DI 库是通用的,可以注入任何对象。既然是对象,有创建,就有销毁,如何实现呢?
这就是 Go 的 interface
的优势所在了:(即鸭子类型) 对象无需显式声明实现了某个接口,只要实现了接口的方法,就是实现了该接口。 (本质上,接口就是一种约定,约定某物有某种行为。那么既然符合了约定,那就没必要使用 implement
一类的关键词来声明,非常灵活)。
回到本项目,我们只需要定义一个 shutdownableService
接口,然后在 getInstance
时,判断是否实现了该接口,如果实现了,就调用 shutdown
方法。代码大致如下:
type
shutdownableService
interface
{
shutdown
()
error
}
func
Shutdown
[
T
any
](
i
*
Injector
)
error
{
// ignore error handling
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="c1">
</span>
<span class="nx">
name
</span>
<span class="o">
:=
</span>
<span class="nx">
generateServiceName
</span>
<span class="p">
[
</span>
<span class="nx">
T
</span>
<span class="p">
]()
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
serviceAny
</span>
<span class="p">
,
</span>
<span class="nx">
ok
</span>
<span class="o">
:=
</span>
<span class="nx">
i
</span>
<span class="p">
.
</span>
<span class="nx">
services
</span>
<span class="p">
[
</span>
<span class="nx">
name
</span>
<span class="p">
]
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
if
</span>
<span class="p">
!
</span>
<span class="nx">
ok
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
fmt
</span>
<span class="p">
.
</span>
<span class="nf">
Errorf
</span>
<span class="p">
(
</span>
<span class="s">
"DI: could not find service `%s`"
</span>
<span class="p">
,
</span>
<span class="nx">
name
</span>
<span class="p">
)
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
service
</span>
<span class="p">
,
</span>
<span class="nx">
ok
</span>
<span class="o">
:=
</span>
<span class="nx">
serviceAny
</span>
<span class="p">
.(
</span>
<span class="nx">
shutdownableService
</span>
<span class="p">
)
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
if
</span>
<span class="nx">
ok
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="nx">
err
</span>
<span class="o">
:=
</span>
<span class="nx">
service
</span>
<span class="p">
.
</span>
<span class="nf">
shutdown
</span>
<span class="p">
()
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
if
</span>
<span class="nx">
err
</span>
<span class="o">
!=
</span>
<span class="kc">
nil
</span>
<span class="p">
{
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="nx">
err
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="nb">
delete
</span>
<span class="p">
(
</span>
<span class="nx">
i
</span>
<span class="p">
.
</span>
<span class="nx">
services
</span>
<span class="p">
,
</span>
<span class="nx">
name
</span>
<span class="p">
)
</span>
</span>
</span>
<span class="line">
<span class="cl">
</span>
</span>
<span class="line">
<span class="cl">
<span class="k">
return
</span>
<span class="kc">
nil
</span>
</span>
</span>
<span class="line">
<span class="cl">
<span class="p">
}
</span>
</span>
</span>
</code>
</pre>
7. 钩子 {#7-钩子}
我们在 Injector
中定义类型为 func(injector *Injector, serviceName string)
的两个钩子,分别在创建对象与销毁对象时,由 framework 调用。
要注意的是,销毁时的钩子调用顺序,类似 defer
,先进后出。
8. 并发 {#8-并发}
主要用了 sync.RWMutex
,细节详见代码。
samber/do 的不足与展望 {#samberdo-的不足与展望}
水平有限,简单谈谈我的看法。
- 所有的对象共用一个锁,对象多的时候,可能会影响性能。所以建议,尽量分散
Injector
,每个Injector
管理的对象数量不要太多。 codegangsta/inject
中拥有SetParent(Injector)
功能,可以实现,先在同层级找寻找该类型依赖,找不到的时候向父级寻找(也即,能基于此实现,子层级覆盖高层级的相同类型实例)。但该库不支持,可能也是因为,samber/do
支持一个类型多个不同名实例,一定程度上可以不需要层级。