golang 包引用之间不允许循环依赖.
循环依赖的本质上是一个错误的设计, 在 golang中 循环依赖是 会产生编译时错误.
golang中为什么不允许循环依赖呢?
1. 没有支持循环导入,目的是迫使 Go 程序员更多地考虑程序的依赖关系.
- 保持依赖关系图的简洁。
- 快速的程序构建。
2. 如果支持循环导入,很容易会造成懒惰、不良的依赖性管理和缓慢的构建。这是设计者不希望看见的。
- 混乱的依赖关系。
- 缓慢的程序构建
golang中的循环依赖对编译的性能 和 程序的依赖关系的清晰非常不利, 所以在程序设计上,要保持 干净的 DAG
.
常见的循环依赖 和 优化代码技巧
1. 抽象顶层包
ddd 的项目结构 一般是 依赖倒置, 在 service层定义接口, infrastrue 层实现,目的是为了解耦, 实现可替换.
你可能将代码写成这样:
repo包代码如下:
repo包依赖 service包
package repo
import (
"context"
"github.com/xxx/cycle/service"
)
type channelRepo struct {
}
func NewChannelRepo() *channelRepo {
return &channelRepo{}
}
func (c channelRepo) GetByID(ctx context.Context, id int64) (*service.Channel, error) {
panic("")
}
service 包
package service
import (
"context"
"github.com/xxx/cycle/repo"
)
type Channel struct {
ID int64
}
type ChannelRepo interface {
GetByID(ctx context.Context, id int64) (*Channel, error)
}
type ChannelService struct {
channelRepo ChannelRepo
}
func NewChannelService() *ChannelService {
return &ChannelService{
channelRepo: repo.NewChannelRepo(),
}
}
func (cs *ChannelService) Get() {
}
解决方式之一是我们可以引入一个顶层包 handler, 依赖 service & repo:
形成如下依赖关系:
package repo
import (
"context"
"github.com/xxx/cycle/service"
)
type channelRepo struct {
}
func NewChannelRepo() *channelRepo {
return &channelRepo{}
}
func (c channelRepo) GetByID(ctx context.Context, id int64) (*service.Channel, error) {
panic("")
}
service包
package service
import (
"context"
)
type Channel struct {
ID int64
}
type ChannelRepo interface {
GetByID(ctx context.Context, id int64) (*Channel, error)
}
type ChannelService struct {
channelRepo ChannelRepo
}
func NewChannelService(channelRepo ChannelRepo) *ChannelService {
return &ChannelService{
channelRepo: channelRepo,
}
}
func (cs *ChannelService) Get() {}
handler包代码如下:
package handler
import (
"github.com/xxx/cycle/repo"
"github.com/xxx/cycle/service"
)
func useCase() {
channelRepo := repo.NewChannelRepo()
channelService := service.NewChannelService(channelRepo)
channelService.Get()
}
2. 和上面的场景类似, 也是抽象一个顶层解决
比如我们没用争取使用策略模式的分包.
有下面这种场景,做 一些数据过滤, 在 filter 包里面定义了filter 接口, 将实现定义在子包里面.
type Filter interface {
PreAction(ctx context.Context, data map[string]interface{}) error
}
1. 子包 word
word包引用了 filter包的 对象 DataContext
package word
import (
"context"
"github.com/xxx/cycle"
)
type wordFilter struct {
}
func (w wordFilter) PreAction(ctx context.Context, data *cycle.DataContext) error {
panic("implement me")
}
func NewWordFilter() *wordFilter {
return &wordFilter{}
}
2. 子包 policy
word包引用了 filter包的 对象 DataContext
package policy
import (
"context"
"github.com/xxx/cycle"
)
type policyFilter struct {
}
func NewPolicyFilter() *policyFilter {
return &policyFilter{}
}
func (p policyFilter) PreAction(ctx context.Context, data *cycle.DataContext) error {
panic("implement me")
}
3. 在filter包中使用
package filter
import (
"context"
"github.com/xxx/cycle/policy"
"github.com/xxx/cycle/word"
)
var filterRegistry map[string]Filter
func init() {
filterRegistry = map[string]Filter{
"word": word.NewWordFilter(),
"policy": policy.NewPolicyFilter(),
}
}
type DataContext struct {
}
type Filter interface {
PreAction(ctx context.Context, data *DataContext) error
}
func GetFilter(filterType string) Filter {
return filterRegistry[filterType]
}
|filter
├── filter.go
├── policy
│ └── filter.go
└── word
└── filter.go
此时产生了循环依赖.
解决的方式: 其实应该属于编程技巧:
不要再 filter包中有使用 filter 的逻辑, filer包仅定义接口, 在 filter包之外进行调用
修改后的方案:
filter包
package filter import ( "context" ) func init() { filterRegistry = make(map[string]Filter) } var filterRegistry map[string]Filter func Register(filterKey string, filter Filter) { filterRegistry[filterKey] = filter } type DataContext struct { } type Filter interface { PreAction(ctx context.Context, data *DataContext) error } func GetFilter(filterType string) Filter { return filterRegistry[filterType]() }
word子包
package word import ( "context" "github.com/xxx/cycle/filter" ) func init() { filter.Register("word", NewWordFilter()) } type wordFilter struct { } func (w wordFilter) PreAction(ctx context.Context, data *filter.DataContext) error { //TODO implement me panic("implement me") } func NewWordFilter() *wordFilter { return &wordFilter{} }
policy子包
package policy import ( "context" "github.com/xxx/cycle/filter" ) func init() { filter.Register("policy", NewPolicyFilter()) } type policyFilter struct { } func NewPolicyFilter() *policyFilter { return &policyFilter{} } func (p policyFilter) PreAction(ctx context.Context, data *filter.DataContext) error { //TODO implement me panic("implement me") }
添加上层调用:
package cycle
import (
"github.com/xxx/cycle/filter"
_ "github.com/xxx/cycle/filter/policy"
_ "github.com/xxx/cycle/filter/word"
)
func handle() {
filter.GetFilter("word")
filter.GetFilter("policy")
}
3. 使用事件总线解耦
想分享的是上面两种方案, 之后又在网上看到了某些方案:
比如事件总线,简单说就是: 使用时如果不关心返回结果, 就可以通过消息的方式解耦.
eventBus 包
package eventBus import ( "github.com/asaskevich/EventBus" ) var globalEventBus EventBus.Bus func init() { globalEventBus = EventBus.New() } func Subscribe(topic string, fn interface{}) error { return globalEventBus.Subscribe(topic, fn) } func SubscribeAsync(topic string, fn interface{}, transactional bool) error { return globalEventBus.SubscribeAsync(topic, fn, transactional) } func Publish(topic string, args ...interface{}) { globalEventBus.Publish(topic, args...) }
packageA
package package_a
import (
"cycle/eventBus"
"fmt"
)
func init() {
eventBus.Subscribe("PrintA", new(PackageA).PrintA)
}
type PackageA struct {
}
func (a PackageA) PrintA() {
fmt.Println("I'm a!")
}
func (a PackageA) PrintAll() {
a.PrintA()
eventBus.Publish("PrintB")
}
- packageB
package package_b
import (
"cycle/eventBus"
"fmt"
)
func init() {
eventBus.Subscribe("PrintB", new(PackageB).PrintB)
}
type PackageB struct {
}
func (b PackageB) PrintB() {
fmt.Println("I'm b!")
}
func (b PackageB) PrintAll() {
b.PrintB()
eventBus.Publish("PrintA")
}
总结
编写代码要符合规范,包的组织结构 和 依赖 要清晰, 符合规范.
golang的包组织规范可以参考: