脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Go1.18 泛型的好、坏亦或丑?

Go1.18 泛型的好、坏亦或丑?

2021-12-29 00:37幽鬼程序员ug Golang

Go 泛型定了,有哪些好的使用场景,哪些不好的应用场景,亦或哪些使用看起来丑?本文聊聊这个问题。

Go1.18 泛型的好、坏亦或丑?

大家好,我是程序员幽鬼。

Go 泛型定了,有哪些好的使用场景,哪些不好的应用场景,亦或哪些使用看起来丑?本文聊聊这个问题。

1 简介

泛型很棒,而且 Go 变得比以前更方便了。但是与可能非常有用的 channel 类似,我们不应该仅仅因为它们存在就到处使用它们。

除了用于数据结构,泛型还有其他很好的应用场景。当然,也有一些不好的用例,比如泛型日志器。还有一些可以使用的解决方案,但相当丑陋,还有一些东西真的很丑。

让我们分别看一个例子!

2 好的应用场景

我真正梦想在 Go 中做的以及我认为我现在终于可以做的是 CRUD 端点的泛型提供程序:

  1. type Model interface {
  2. ID() string
  3. }
  4. type DataProvider[MODEL Model] interface {
  5. FindByID(id string) (MODEL, error)
  6. List() ([]MODEL, error)
  7. Update(id string, model MODEL) error
  8. Insert(model MODEL) error
  9. Delete(id string) error
  10. }

这是一个大接口,你可以根据具体用例的需要缩短它,但是,为了完整性起见,我们暂时就这么写。

现在你可以定义一个使用 DataProvider 的 HTTP 处理程序:

  1. type HTTPHandler[MODEL Model] struct {
  2. dataProvider DataProvider[MODEL]
  3. }
  4. func (h HTTPHandler[MODEL]) FindByID(rw http.ResponseWriter, req *http.Request) {
  5. // validate request here
  6. id = // extract id here
  7. model, err := h.dataProvider.FindByID(id)
  8. if err != nil {
  9. // error handling here
  10. return
  11. }
  12. err = json.NewEncoder(rw).Encode(model)
  13. if err != nil {
  14. // error handling here
  15. return
  16. }
  17. }

如你所见,我们可以为每个方法实现一次,然后我们就完成了。我们甚至可以在事物的另一端创建一个客户端,我们只需要为基本方法实现一次。

为什么我们在这里使用泛型而不是简单的我们已经定义的 Model 接口?

与在此处使用 Model 类型本身相比,泛型有一些优点:

  • 使用泛型方法,DataProvider 根本不需要知道 Model,也不需要实现它。它可以简单地提供非常强大的具体类型(但仍然可以为简单的用例抽象)
  • 我们可以扩展这个解决方案并使用具体类型进行操作。让我们看看插入或更新的验证器会是什么样子。
  1. type HTTPHandler[MODEL any] struct {
  2. dataProvider DataProvider[MODEL]
  3. InsertValidator func(new MODEL) error
  4. UpdateValidator func(old MODEL, new MODEL) error
  5. }

在这个验证器中是泛型方法的真正优势所在。我们将解析 HTTP 请求,如果定义了自定义的 InsertValidator,那么我们可以使用它来验证模型是否检出,我们可以以类型安全的方式进行并使用具体模型:

  1. type User struct {
  2. FirstName string
  3. LastName string
  4. }
  5. func InsertValidator(u User) error {
  6. if u.FirstName == "" { ... }
  7. if u.LastName == "" { ... }
  8. }

所以我们有一个泛型的处理器,我们可以用自定义回调来调整它,它直接为我们获取有效负载。没有类型转换。没有 map。只有结构体本身!

3 不好的应用场景

一起看一个泛型日志器的例子:

  1. type GenericLogger[T any] interface {
  2. WithField(string, string) T
  3. Info(string)
  4. }

这本身还不是很有用。有更简单的方法可以将键值字符串对添加到日志器,并且没有日志器(据我所知)实际实现此接口。我们也不需要新的日志标准。如果我们想使用 logrus[1],我们必须这样做:

  1. type GenericLogger[T any, FIELD map[string]interface{}] interface{
  2. WithFields(M) T
  3. Info(string)
  4. }

如果我们添加自引用部分,这实际上可能由 logrus 日志器实现。但是,让我们考虑在实际结构体中使用它,例如某种处理程序:

  1. type MessageHandler[T GenericLogger[T], FIELD map[string]interface{}] struct {
  2. logger GenericLogger[T, FIELD]
  3. }

为了在结构体中使用这个日志器,我们需要使我们的结构体泛型,这仅适用于日志器。如果 MessageHandler 本身正在处理泛型消息,那将变成第三个类型参数!

到目前为止,甚至没有办法将其分配给具有泛型的变量。所以,尽管我们可以用一个接口来表示这个日志器很棒,但我实际上建议不要这样做。而我最喜欢的日志库 (zap[2]),由于其字段的性质,甚至无法用它来表示。

4 丑的场景

当我使用泛型时,我发现缺少对在方法中引入新泛型参数的支持。虽然这可能有很好的理由,但它确实需要一些解决方法。让我们想象一下我们想要将一个 map 简化为一个整数。理想情况下,我们将通过使用返回新泛型参数的方法来完成此操作,然后我们可以简单地提供 map reduce 函数。

那么,当我们仍然想以泛型方式缩小该 map 时,我们该怎么办?既然没有方法,那么让我们创建一个方法:

  1. type GenericMap[KEY comparable, VALUE any] map[KEY]VALUEfunc (g GenericMap[KEY, VALUE]) Values() []VALUE {
  2. values := make([]VALUE, len(g))
  3. for _, v := range g {
  4. values = append(values, v)
  5. }
  6. return values
  7. }
  8. func Reduce[KEY comparable, VALUE any, RETURN any](g GenericMap[KEY, VALUE], callback func(RETURN, KEY, VALUE "KEY comparable, VALUE any, RETURN any") RETURN) RETURN {
  9. var r RETURN
  10. for k, v := range g {
  11. r = callback(r, k, v)
  12. }
  13. return r
  14. }

GenericMap 成为第一个参数或我们的 Reduce 函数。在这种情况下,你可以使用任何类型的 map 作为第一个参数,而不是 GenericMap。然而,我想说明的一点是,如果这个方法本身是 GenericMap 的一部分,那就太好了。即使不是,我们仍然可以模仿这种行为。总的来说,我可能仍会在某些用例中使用这种模式,即使它实际上很丑陋。

5 真的很丑

有时你可能想要使用工厂模式,它为你提供诸如 DataProviders 之类的东西。你可能希望在动态注册的端点上获取提供程序。所以你可以这样做:

  1. type DataProviderFactory struct {
  2. dataProviders map[providerKey]any
  3. }
  4. func ProviderByName[MODEL Model](factory *DataProviderFactory, name string "MODEL Model") (DataProvider[MODEL], bool) {
  5. var m MODEL
  6. prov, has := factory.dataProviders[providerKey{name: name, typ: reflect.TypeOf(m)}]
  7. if !has {
  8. return nil, false
  9. }
  10. return prov.(DataProvider[MODEL]), true
  11. }
  1. func RegisterProvider[MODEL Model](factory *DataProviderFactory, name string, p DataProvider[MODEL] "MODEL Model") {
  2. var m MODEL
  3. factory.dataProviders[providerKey{name: name, typ: reflect.TypeOf(m)}] = p
  4. }

虽然这有效,虽然它可能有用,但它是很丑。它将丑陋(反射)与更丑陋(泛型)的东西结合在一起。

虽然从技术上讲这应该是类型安全的,但由于我们的复合键具有名称和反射类型,它仍然很难看。我是否要把它放在生产代码的任何地方,我会很纠结。

6 总结

虽然我喜欢泛型,但我认为很难取得平衡,尤其是在开始的时候。所以我们需要确保记住它们为什么存在,在什么情况下我们应该使用它们,什么时候我们应该避免它们!

原文链接:https://itnext.io/golang-1-18-generics-the-good-the-bad-the-ugly-5e9fa2520e76

参考资料

[1]logrus: https://github.com/sirupsen/logrus

[2]zap: https://github.com/uber-go/zap

本文转载自微信公众号「幽鬼」,可以通过以下二维码关注。转载本文请联系幽鬼公众号。

Go1.18 泛型的好、坏亦或丑?

原文链接:https://mp.weixin.qq.com/s/Xp7B3okOOcNedhNMM1pU3Q

延伸 · 阅读

精彩推荐
  • GolangGolang 语言极简类型转换库cast的使用详解

    Golang 语言极简类型转换库cast的使用详解

    本文我们通过 cast.ToString() 函数的使用,简单介绍了cast 的使用方法,除此之外,它还支持很多其他类型,在这没有多多介绍,对Golang 类型转换库 cast相关知...

    Golang语言开发栈6112021-12-02
  • GolangGolang实现四种负载均衡的算法(随机,轮询等)

    Golang实现四种负载均衡的算法(随机,轮询等)

    本文介绍了示例介绍了Golang 负载均衡的四种实现,主要包括了随机,轮询,加权轮询负载,一致性hash,感兴趣的小伙伴们可以参考一下...

    Gundy_8442021-08-09
  • GolangGo语言实现自动填写古诗词实例代码

    Go语言实现自动填写古诗词实例代码

    这篇文章主要给大家介绍了关于Go语言实现自动填写古诗词的相关资料,这是最近在项目中遇到的一个需求,文中通过示例代码介绍的非常详细,需要的朋...

    FengY5862020-05-14
  • GolangGO语言字符串处理Strings包的函数使用示例讲解

    GO语言字符串处理Strings包的函数使用示例讲解

    这篇文章主要为大家介绍了GO语言字符串处理Strings包的函数使用示例讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加...

    Jeff的技术栈6882022-04-14
  • Golang深入浅析Go中三个点(...)用法

    深入浅析Go中三个点(...)用法

    这篇文章主要介绍了深入浅析Go中三个点(...)用法,需要的朋友可以参考下...

    踏雪无痕SS6472021-11-17
  • Golanggo语言获取系统盘符的方法

    go语言获取系统盘符的方法

    这篇文章主要介绍了go语言获取系统盘符的方法,涉及Go语言调用winapi获取系统硬件信息的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    无尽海3862020-04-24
  • GolangGo语言基础单元测试与性能测试示例详解

    Go语言基础单元测试与性能测试示例详解

    这篇文章主要为大家介绍了Go语言基础单元测试与性能测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步...

    枫少文7812021-12-05
  • GolangGo语言range关键字循环时的坑

    Go语言range关键字循环时的坑

    今天小编就为大家分享一篇关于Go语言range关键字循环时的坑,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来...

    benben_20154202020-05-23