新Gopher来自Java土地。
假设我有一个通用的存储接口:
package repositories
type Repository interface {
Get(key string) string
Save(key string) string
}
通过在单独的包中实现此接口,我支持多个不同的后端(Redis,Boltdb等)。但是,每个实现都有唯一的配置值需要传入。所以我在每个包中定义一个构造函数,如:
package redis
type Config struct {
...
}
func New(config *Config) *RedisRepository {
...
}
和
package bolt
type Config struct {
...
}
func New(config *Config) *BoltRepository {
...
}
main.go
读取类似于:
type AppConfig struct {
DatabaseName string,
BoltConfig *bolt.Config,
RedisConfig *redis.Config,
}
根据DatabaseName
的值,应用程序将实例化所需的存储库。做这个的最好方式是什么?我在哪里做?现在我正在做一些可怕的工厂方法,看起来非常像Go反模式。
在我的main.go中,我有一个函数读取上面反映的配置值,根据BoltConfig
的值选择正确的配置(RedisConfig
或DatabaseName
):
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)
}
是的,第二次我完成了键入这个代码我因为我带入这个世界的恐怖感而退缩。有人可以帮我逃避这个工厂吗?有什么更好的方法来做这种事情 - 即在运行时选择接口实现。
答案 0 :(得分:2)
您需要动态注册吗?由于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
中保存几行就不算什么了。
如果此代码由多个程序使用,则将getRepo
和AppConfig
移至repos
包可能是有意义的。