习惯于Java或者C#开发的人应该对控制反转与依赖注入应该再熟悉不过了。在Java平台有鼎鼎大名的Spring框架,在C#平台有Autofac,Unity,Windsor等,我当年C#开发时用的最多的就是Windsor。使用IoC容器是面向对象开发中非常方便的解耦模块之间的依赖的方法。各个模块之间不依赖于实现,而是依赖于接口,然后在构造函数或者属性或者方法中注入特定的实现,方便了各个模块的拆分以及模块的独立单元测试。
在[长安链]的设计中,各个模块可以灵活组装,模块之间的依赖基于protocol中定义的接口,每个接口有一个或者多个官方实现,当然第三方也可以提供该接口更多的实现。为了实现更灵活的组装各个模块,管理各个模块的依赖关系,于是我写了iocgo这个轻量级的golang版Ioc容器。
现在go官方版本已经出到1.17了,当然我在代码中其实也没有用什么新版本的新特性,于是就用1.15版本或者之后的Go版本即可。要使用iocgo包,直接通过go get添加到项目中:
go get github.com/studyzy/iocgo
type Fooer interface { Foo(int) } type Foo struct { } func (Foo)Foo(i int) { fmt.Println("foo:",i) } type Barer interface { Bar(string) } type Bar struct { } func (Bar) Bar(s string){ fmt.Println("bar:",s) } type Foobarer interface { Say(int,string) } type Foobar struct { foo Fooer bar Barer } func NewFoobar(f Fooer,b Barer) Foobarer{ return &Foobar{ foo: f, bar: b, } } func (f Foobar)Say(i int ,s string) { f.foo.Foo(i) f.bar.Bar(s) } func TestContainer_SimpleRegister(t *testing.T) { container := NewContainer() container.Register(NewFoobar) container.Register(func() Fooer { return &Foo{} }) container.Register(func() Barer { return &Bar{} }) var fb Foobarer container.Resolve(&fb) fb.Say(123,"Hello World") }
这里我使用NewContainer()创建了一个新的容器,然后在容器中调用Register方法注册了3个接口和对应的构造函数,分别是:
接下来调用Resolve函数,并传入var fb Foobarer 这个接口变量的指针,iocgo就会自动去构建Foobarer对应的实例,并最终将实例赋值到fb这个变量上,于是最后我们就可以正常调用fb.Say实例方法了。
iocgo的注册interface到对象的函数定义如下:
func Register(constructor interface{}, options ...Option) error
iocgo为Register函数提供了以下参数选项可根据实际情况选择性使用:
如果我们已经有了某个对象的实例,那么可以将该实例和其想映射的interface直接注册到ioc容器中,方便其他依赖的对象获取,RegisterInstance函数定义如下:
RegisterInstance(interfacePtr interface{}, instance interface{}, options ...Option) error
使用上也很简单,直接将实例对应的interface的指针作为参数1,实例本身作为参数2,传入RegisterInstance即可:
b := &Bar{} var bar Barer //interface container.RegisterInstance(&bar, b) // register interface -> instance
相关映射我们通过Register函数和RegisterInstance函数已经注册到容器中,接下来就需要从容器获得指定的实例了。获得实例需要调用函数:
func Resolve(abstraction interface{}, options ...ResolveOption) error
这里第一个参数abstraction是我们想要获取的某个interface的指针,第二个参数是可选参数,目前提供的选项有:
var fb Foobarer err:=container.Resolve(&fb)
另外如果我们的构造函数return的值中支持error,而且实际构造的时候确实返回了error,那么Resolve函数也会返回对应的这个err。
特别注意:Resolve的第一个参数是申明的某个interface的指针,一定要是指针,不能直接传interface
有些时候构造函数的入参非常多,于是我们可以申明一个结构体,把所有入参都放入这个结构体中,这样构造函数就只需要一个参数了。iocgo也支持自动填充这个结构体中interface对应的实例,从而构造新的对象。另外iocgo也提供了Fill方法,可以直接填充某个结构体,比如:
type FoobarInput struct { foo Fooer bar Barer msg string } input := FoobarInput{ msg: "studyzy", } container.Register(func() Fooer { return &Foo{} }) container.Register(func() Barer { return &Bar{} }) err := container.Fill(&input)
结构体中的字段还支持tag,目前提供的tag有两种:
type FoobarInputWithTag struct { foo Fooer `optional:"true"` bar Barer `name:"baz"` msg string }
除了构造函数注入之外,iocgo也支持函数注入,我们申明一个函数,这个函数的参数中有些参数是interface,那么通过调用iocgo中的Call方法,可以为这个函数注入对应的实例作为参数,并最终完成函数的调用。
示例 example:
func SayHi1(f Fooer, b Barer) { f.Foo(1234) b.Bar("hi") } Register(func() Fooer { return &Foo{} }) Register(func() Barer { return &Bar{} }) Call(SayHi1)
Call函数也是支持选项的,目前提供了2个选项:
在写这个iocgo的代码时,主要参考了以下两个Ioc相关的项目:
iocgo是一个纯Golang语言开发的用于管理依赖注入的IoC容器,使用这个容器可以很好的实现go语言下的面向对象开发,模块解耦。现已经开源,欢迎大家使用,开源地址:https://github.com/studyzy/iocgo