gpt4 book ai didi

go - 根据动态配置值实例化接口(interface)实现

转载 作者:数据小太阳 更新时间:2023-10-29 03:05:03 26 4
gpt4 key购买 nike

来自 Java 领域的新 Gopher。

假设我有一些通用的存储接口(interface):

package repositories

type Repository interface {
Get(key string) string
Save(key string) string
}

我通过在单独的包中实现此接口(interface)来支持多个不同的后端(Redis、Boltdb 等)。但是,每个实现都有需要传入的唯一配置值。所以我在每个包中定义了一个构造函数,例如:

package redis 

type Config struct {
...
}

func New(config *Config) *RedisRepository {
...
}

package bolt

type Config struct {
...
}

func New(config *Config) *BoltRepository {
...
}

main.go 读取一个类似于以下内容的 json 配置文件:

type AppConfig struct {
DatabaseName string,
BoltConfig *bolt.Config,
RedisConfig *redis.Config,
}

根据 DatabaseName 的值,应用程序将实例化所需的存储库。做这个的最好方式是什么?我在哪里做?现在我正在做某种可怕的 factoryfactory 方法,它看起来非常像 Go 反模式。

在我的 main.go 中,我有一个函数可以读取上面反射(reflect)的配置值,根据值选择正确的配置(BoltConfigRedisConfig) 数据库名称:

func newRepo(c reflect.Value, repoName string) (repositories.Repository, error) {
t := strings.Title(repoName)

repoConfig := c.FieldByName(t).Interface()

repoFactory, err := repositories.RepoFactory(t)
if err != nil {
return nil, err
}

return repoFactory(repoConfig)
}

在我的 repositories 包中,我有一个查找存储库类型并返回一个生成实例化存储库的工厂函数的工厂:

func RepoFactory(provider string) (RepoProviderFunc, error) {
r, ok := repositoryProviders[provider]
if !ok {
return nil, fmt.Errorf("repository does not exist for provider: %s", r)
}
return r, nil
}

type RepoProviderFunc func(config interface{}) (Repository, error)

var ErrBadConfigType = errors.New("wrong configuration type")

var repositoryProviders = map[string]RepoProviderFunc{
redis: func(config interface{}) (Repository, error) {
c, ok := config.(*redis.Config)
if !ok {
return nil, ErrBadConfigType
}
return redis.New(c)
},
bolt: func(config interface{}) (Repository, error) {
c, ok := config.(*bolt.Config)
if !ok {
return nil, ErrBadConfigType
}
return bolt.New(c)
},
}

将它们放在一起,我的 main.go 看起来像:

cfg := &AppConfig{}
err = json.Unmarshal(data, cfg)
if err != nil {
log.Fatalln(err)
}

c := reflect.ValueOf(*cfg)

repo, err := newRepo(c, cfg.DatabaseName)
if err != nil {
log.Fatalln(err)
}

是的,当我输入完这段代码后,我对自己给这个世界带来的恐惧退缩了。有人可以帮我逃离这个工厂 hell 吗?做这种事情的更好方法是什么——即在运行时选择接口(interface)实现。

最佳答案

需要动态注册吗?由于 AppConfig 类型,后端列表似乎已经嵌入到您的服务器中,因此您最好只编写尽可能简单的工厂代码:

func getRepo(cfg *AppConfig) (Repository, error) {
switch cfg.DatabaseName {
case "bolt":
return bolt.New(cfg.BoltConfig), nil
case "redis":
return redis.New(cfg.RedisConfig), nil
}
return nil, fmt.Errorf("unknown database: %q", cfg.DatabaseName)
}

func main() {
...
var cfg AppConfig
if err := json.Unmarshal(data, &cfg); err != nil {
log.Fatalf("failed to parse config: %s", err)
}
repo, err := getRepo(&cfg)
if err != nil {
log.Fatalln("repo construction failed: %s", err)
}
...

当然,您可以将其替换为通用的基于反射的代码。但是,虽然这可以节省几行重复的代码,并且在添加新后端时无需更新 getRepo,但它会引入一团困惑的抽象,您将不得不编辑代码无论如何,如果您引入了一个新的后端(例如,扩展您的 AppConfig 类型),那么在 getRepo 中节省几行就很难节省了。

如果此代码被多个程序使用,将 getRepoAppConfig 移动到 repos 包中可能是有意义的。

关于go - 根据动态配置值实例化接口(interface)实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43082936/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com