使用泛型改造 config 包

在 Go 还不支持泛型的时代,我们对于多种类型的应对一般是写出适用于各种类型的方法,然后按需要获取的类型来调用。

这样做虽然简单且有效,但大量重复的代码总是让人觉得不够优雅。而现在,Go 1.18 推出的泛型可以很好的解决这一问题。

比如 G02 课程中的 config 包,原文使用了一系列 GetStringGetBoolGetInt 的方法来支持各种类型配置的获取。

在 Go 1.18 版本中,我们可以利用泛型,只用一个方法来实现上述的效果:

func Get[T any](path string, defaultValue ...interface{}) T {
    if value := internalGet(path, defaultValue...); value != nil {
        return value.(T)
    }
    // 泛型不能返回 nil,因此需要根据类型建立空变量,这样返回的会是对应类型的"空"值
    var fallback T
    return fallback
}

在使用的时候就可以直接:

config.Get[string]("app.name")
config.Get[int]("app.level")
config.Get[bool]("app.debug")

如果你对于泛型还不了解,推荐阅读下 Go 1.18 泛型全面讲解:一篇讲清泛型的全部,个人觉得写得挺详细的。

本帖已被设为精华帖!
本帖由系统于 2年前 自动加精
sunxyw
讨论数量: 4

defaultValue ...interface {} 改成 T, 更加优化

2年前 评论

viper.Get 返回的是 sting 类型数据,如果.env 设置了配置需要取 int 类型,按这种写法 Get [int] 的话就会 interface {} is string, not int

2年前 评论

这样感觉不行,类型转换那感觉有问题
比如这样的转换?

func main() {
    var t interface{} = 2.33333
    // var p = fmt.Println

    //panic
    p(t.(bool))
    p(t.(float32))
    p(t.(int))
}
2年前 评论
wuvikr

两个问题:

  1. 使用 any 进行泛型约束太宽泛了。
  2. 返回默认零值,调用者无法区分这个零值是配置项本身的值,还是因为配置项不存在而返回的默认零值。
代码已被折叠,点此展开

调用:

// 运行服务
    err := r.Run(":" + config.GetWithDefault[string]("app.port", "8889"))
    if err != nil {
        panic(err)
    }
}

给 Get 函数添加了一个 bool 类型的返回值,调用这可以通过布尔值明确知道配置项是否存在,然后把默认配置项分离出来成为一个单独的 GetWithDefault 函数,代码的逻辑更清晰,调用者在使用的时候也更灵活的去选择。
对于必须存在的初始化配置,还可以增加 MustGet 函数:

// MustGet 获取配置项,如果失败则panic
func MustGet[T ConfigValue](path string) T {
    value, ok := Get[T](path)
    if !ok {
        panic(fmt.Sprintf("配置项 [%s] 不存在或类型转换失败", path))
    }
    return value
}
4个月前 评论