Golang 控制反转 (IOC)在工程中应用
背景
最近在总结现有框架中一些比较有亮点的地方,个人觉得带着问题去学习是比较高效的事情,所以把一些学习总结记录下来。
IoC是一种设计原理,比较常见于面向对象的设计中反转控制,实现应用程序类之间的松散耦合。详细的设计模式就不再这里赘述了
设计
采用的第三方库:https://github.com/berkaroad/ioc
使用起来还是比较简单的,无非就是RegisterTo, Invoke,但是任何的库都需要结合框架起来才有意义。
一提到松耦合,在GO中很容易就想到接口(interface),所以我们用接口实现的各个层之间的松耦合。
按照传统的MVC框架,一般服务端会有几种分层,Controler层、Service层、Module层 从上到下,如何将Ioc结合在框架中才是值得探讨的事情。
目录
调用结构:由于没有服务,main函数充当的是Controler、Service是服务层、Module是数据层、Resource是存储层、app是各种接口的定义
main-->Service-->Module-->Resource
为了演示服务之间的调用,我们定义了service1和service2两种服务
实现
各层的接口定义
package app
type Service1 interface {
AddData(string)
DelData(string)
}
type Service2 interface {
AddData(string)
DelData(string)
}
type Module interface {
DataToSave(string)
DataToRemove(string)
}
type Resource interface {
Save(string)
Remove(string)
}
IOC 初始化
package app
import (
"github.com/berkaroad/ioc"
"github.com/spf13/viper"
)
func GetOrCreateRootContainer() ioc.Container {
v := viper.Get("runtime.container")
if v == nil {
v = ioc.NewContainer()
viper.Set("runtime.container", v)
}
return v.(ioc.Container)
}
这里其实怎么实现都行,只是一个单例NewContainer就可以
存储层(自下而上)
package resource
import (
"fmt"
"github.com/berkaroad/ioc"
"github.com/zhaoshoucheng/hodgepodge/IoC/app"
)
type ResourceObj struct {
name string
}
func (r *ResourceObj) Save(str string) {
fmt.Println(r.name, " Save ", str)
}
func (r *ResourceObj) Remove(str string) {
fmt.Println(r.name, " Remove ", str)
}
func init() {
mo := &ResourceObj{name: "mongo"}
// static assert 静态断言类型检测
func(t app.Resource) {}(mo)
app.GetOrCreateRootContainer().RegisterTo(mo, (*app.Resource)(nil), ioc.Singleton)
//rd := &ResourceObj{name: "redis"} 实现是用的map,所以mong会被覆盖
//app.GetOrCreateRootContainer().RegisterTo(rd, (*app.Resource)(nil), ioc.Singleton)
}
RegisterTo是注册过程,在mo对象后续会当作app.Resource接口的实现来使用,其底层实现是一个map
数据层
package module
import (
"fmt"
"github.com/berkaroad/ioc"
"github.com/zhaoshoucheng/hodgepodge/IoC/app"
)
var (
rs app.Resource
)
type ModuleObj struct {
}
func (mo *ModuleObj) DataToSave(str string) {
fmt.Println("ModuleObj DataToSave ", str)
rs.Save(str)
}
func (mo *ModuleObj) DataToRemove(str string) {
fmt.Println("ModuleObj DataToRemove ", str)
rs.Remove(str)
}
func init() {
mo := &ModuleObj{}
// static assert 静态断言类型检测
func(t app.Module) {}(mo)
app.GetOrCreateRootContainer().RegisterTo(mo, (*app.Module)(nil), ioc.Singleton)
app.GetOrCreateRootContainer().Invoke(func(r app.Resource) {
rs = r
})
}
因为我们之前app.Resource已经注册过,所以这里Invoke的时候就可以获取到实现该接口的对象
服务层
package service
import (
"fmt"
"github.com/berkaroad/ioc"
"github.com/zhaoshoucheng/hodgepodge/IoC/app"
)
var (
module app.Module
service2 app.Service2
)
type Service1 struct {
}
func (s1 *Service1) AddData(str string) {
service2.AddData(str)
fmt.Println("Service1 AddData ", str)
module.DataToSave(str)
}
func (s1 *Service1) DelData(str string) {
service2.DelData(str)
fmt.Println("Service1 DelData ", str)
module.DataToRemove(str)
}
func init() {
s1 := &Service1{}
s2 := &Service2{}
service2 = s2
//static assert 静态断言做类型检查
func(t app.Service1) {}(s1)
func(t app.Service2) {}(s2)
app.GetOrCreateRootContainer().RegisterTo(s1, (*app.Service1)(nil), ioc.Singleton)
app.GetOrCreateRootContainer().RegisterTo(s2, (*app.Service2)(nil), ioc.Singleton)
app.GetOrCreateRootContainer().Invoke(func(mod app.Module) {
module = mod
})
}
Main
package main
import (
"github.com/zhaoshoucheng/hodgepodge/IoC/app"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/resource"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/module"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/service"
)
func main() {
var s1 app.Service1
app.GetOrCreateRootContainer().Invoke(func(service app.Service1) {
s1 = service
})
s1.AddData("IOC Test")
}
测试
思考
我们为什么要用到Ioc呢?个人感觉有几点好处
1.解决各种依赖问题,写GO可能都遇到过循环引用问题,越是复杂的系统就越有可能出现这种混乱的调用现象。
2.实现了很好的扩展性,如果存储层想从redis切换到mongo,定义一个相同的对象,替换注册对象就可以轻松实现。
3.易使用,随时随地可以通过Invoke获取相应的接口对象。
问题
难道就没有问题吗?
当然有,就是引用顺序的问题,也就是先register 还是先invoke 这个在例子中感觉很简单,但是在工程中很容易出错
_ "github.com/zhaoshoucheng/hodgepodge/IoC/module"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/resource"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/service"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/resource"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/module"
_ "github.com/zhaoshoucheng/hodgepodge/IoC/service"
第一种写法就会崩溃,第二种正确
原因第一种module 的init 先执行,app.Resource的对象还没有注册。所以init的先后顺序很重要
但这个是凭借字节码进行的排序,有时IDE还不让我们改,所以需要一些控制器去处理这种情况,
可以看下篇的应用Go语言实现一个程序流程控制器去解决这个问题
本次代码:https://github.com/zhaoshoucheng/hodgepodge/tree/main/IoC