您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

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

时间:2021-12-29 11:41:29  来源:Go语言中文网  作者:

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

01 简介

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

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

让我们分别看一个例子!

02 好的应用场景

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

type Model interface {
    ID() string
}

type DataProvider[MODEL Model] interface {
    FindByID(id string) (MODEL, error)
    List() ([]MODEL, error)
    Update(id string, model MODEL) error
    Insert(model MODEL) error
    Delete(id string) error
}

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

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

type HTTPHandler[MODEL Model] struct {
    dataProvider DataProvider[MODEL]
}

func (h HTTPHandler[MODEL]) FindByID(rw http.ResponseWriter, req *http.Request) {
    // validate request here
    id = // extract id here
    model, err := h.dataProvider.FindByID(id)
    if err != nil {
        // error handling here
        return
    }
    err = json.NewEncoder(rw).Encode(model)
    if err != nil {
        // error handling here
        return
    }
}

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

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

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

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

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

type User struct {
    FirstName string
    LastName string
}

func InsertValidator(u User) error {
    if u.FirstName == "" { ... } 
    if u.LastName == "" { ... }
}

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

03 不好的应用场景

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

type GenericLogger[T any] interface {
    WithField(string, string) T
    Info(string)
}

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

type GenericLogger[T any, FIELD map[string]interface{}] interface{
    WithFields(M) T
    Info(string)
}

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

type MessageHandler[T GenericLogger[T], FIELD map[string]interface{}] struct {
    logger GenericLogger[T, FIELD]
}

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

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

04 丑的场景

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

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

type GenericMap[KEY comparable, VALUE any] map[KEY]VALUEfunc (g GenericMap[KEY, VALUE]) Values() []VALUE {
    values := make([]VALUE, len(g))
    for _, v := range g {
        values = Append(values, v)
    }
    return values
}

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 {
    var r RETURN
    for k, v := range g {
        r = callback(r, k, v)
    }
    return r
}

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

05 真的很丑

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

type DataProviderFactory struct {
    dataProviders map[providerKey]any
}
func ProviderByName[MODEL Model](factory *DataProviderFactory, name string "MODEL Model") (DataProvider[MODEL], bool) {
        var m MODEL
    prov, has := factory.dataProviders[providerKey{name: name, typ: reflect.TypeOf(m)}]
    if !has {
       return nil, false
    }
    return prov.(DataProvider[MODEL]), true 
}
func RegisterProvider[MODEL Model](factory *DataProviderFactory, name string, p DataProvider[MODEL] "MODEL Model") {
    var m MODEL
    factory.dataProviders[providerKey{name: name, typ: reflect.TypeOf(m)}] = p 
}

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

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

06 总结

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

原文链接: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



Tags:泛型   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
Go 泛型定了,有哪些好的使用场景,哪些不好的应用场景,亦或哪些使用看起来丑?本文聊聊这个问题。01 简介泛型很棒,而且 Go 变得比以前更方便了。但是与可能非常有用的 channel 类...【详细内容】
2021-12-29  Tags: 泛型  点击:(0)  评论:(0)  加入收藏
大家好,我是 polarisxu。前段时间,Russ Cox 明确了泛型相关的事情,原计划在标准库中加入泛型相关的包,改放到 golang.org/x/exp 下。目前,Go 泛型的主要设计者 ianlancetaylor 完...【详细内容】
2021-11-30  Tags: 泛型  点击:(25)  评论:(0)  加入收藏
从面向对象说起Java作为一门面相对象的语言,当然是支持面相对象的三大基本特性的,反手就蹦出三个词:封装、继承、多态。我们假设有三个类,动物、猫、狗。父类是动物Animal,有两...【详细内容】
2020-12-29  Tags: 泛型  点击:(141)  评论:(0)  加入收藏
一、引入泛型在实际开发中,常常会出现重复代码的问题,例如。 运行结果这里的三个方法,使用不同的类型干了同样的事情。作为一个有追求的程序员,是绝不允许这样的事情发生的。我...【详细内容】
2020-08-26  Tags: 泛型  点击:(81)  评论:(0)  加入收藏
在学习泛型之前我们先回顾下Java的数据类型以及涉及到的一些概念。Java数据类型Java的两大数据类型分为基础类型和引用类型。基本类型的数值不是对象,不能调用对象的toString...【详细内容】
2020-08-20  Tags: 泛型  点击:(58)  评论:(0)  加入收藏
一、集合概述当我们在使用java进行编程开发时,经常会遇到一些需要集中存放的多个数据,这时我们可以选择“数组”或者“集合”,关于数组的知识可以参考上一篇文章,今天我们主要讲...【详细内容】
2020-08-14  Tags: 泛型  点击:(42)  评论:(0)  加入收藏
你没看错,这里讲的就是 Go 中的泛型。只不过还没有正式发布,是基于草案设计的,已经是实现了可运行的版本。所以,泛型到来真的不远了!Go 中的泛型已经接近成为现实。本文讲述的是...【详细内容】
2020-04-26  Tags: 泛型  点击:(78)  评论:(0)  加入收藏
对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。本文参考java 泛型详解、Java中的泛型方法、 java泛型详解1....【详细内容】
2020-01-03  Tags: 泛型  点击:(64)  评论:(0)  加入收藏
引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用。本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类型擦除。作...【详细内容】
2019-08-07  Tags: 泛型  点击:(245)  评论:(0)  加入收藏
▌简易百科推荐
Go 泛型定了,有哪些好的使用场景,哪些不好的应用场景,亦或哪些使用看起来丑?本文聊聊这个问题。01 简介泛型很棒,而且 Go 变得比以前更方便了。但是与可能非常有用的 channel 类...【详细内容】
2021-12-29    Go语言中文网  Tags:泛型   点击:(0)  评论:(0)  加入收藏
zip 是一种常见的归档格式,本文讲解 Go 如何操作 zip。首先看看 zip 文件是如何工作的。以一个小文件为例:(类 Unix 系统下)$ cat hello.textHello!执行 zip 命令进行归档:$ zip...【详细内容】
2021-12-17  Go语言中文网    Tags:Go语言   点击:(13)  评论:(0)  加入收藏
大家好,我是 polarisxu。前段时间,Russ Cox 明确了泛型相关的事情,原计划在标准库中加入泛型相关的包,改放到 golang.org/x/exp 下。目前,Go 泛型的主要设计者 ianlancetaylor 完...【详细内容】
2021-11-30  Go语言中文网    Tags:slices 包   点击:(25)  评论:(0)  加入收藏
前言最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼”真香“。 但现阶段相对来说还是 Python 写的多一些,偶尔还...【详细内容】
2021-11-25  crossoverJie    Tags:Go   点击:(31)  评论:(0)  加入收藏
go-micro是基于 Go 语言用于开发的微服务的 RPC 框架,主要功能如下:服务发现,负载均衡 ,消息编码,请求/响应,Async Messaging,可插拔接口,最后这个功能牛p安装步骤安装proto...【详细内容】
2021-09-06    石老师小跟班  Tags:go-micro   点击:(198)  评论:(0)  加入收藏
GoLand 2021.2 EAP 5 现已发布。用户可以从工具箱应用程序中获得 EAP 构建,也可以从官方网站手动下载。并且从此 EAP 开始,只有拥有有效的 JetBrains 帐户才能加入该计划。手...【详细内容】
2021-06-29  IT实战联盟  今日头条  Tags:GoLand   点击:(186)  评论:(0)  加入收藏
作者:HDT3213今天给大家带来的开源项目是 Godis:一个用 Go 语言实现的 Redis 服务器。支持: 5 种数据结构(string、list、hash、set、sortedset) 自动过期(TTL) 发布订阅、地理位...【详细内容】
2021-06-18  HelloGitHub  今日头条  Tags:Go   点击:(125)  评论:(0)  加入收藏
统一规范篇合理规划目录本篇主要描述了公司内部同事都必须遵守的一些开发规矩,如统一开发空间,既使用统一的开发工具来保证代码最后的格式的统一,开发中对文件和代码长度的控制...【详细内容】
2021-05-18  1024课堂    Tags:Go语言   点击:(233)  评论:(0)  加入收藏
闭包概述 闭包不是Go语言独有的概念,在很多编程语言中都有闭包 闭包就是解决局部变量不能被外部访问的一种解决方案 是把函数当作返回值的一种应用 代码演示总体思想:在函数...【详细内容】
2021-05-14  HelloGo  今日头条  Tags:Go语言   点击:(223)  评论:(0)  加入收藏
一时想不开,想了解一下Go语言,于是安装了并体验了一下。下载1. 进入golang.google.cn 点击Download Go 2.选择对应的操作系统,点击后开始下载。 安装1. windows下执行傻瓜式安...【详细内容】
2021-05-12  程序员fearlazy  fearlazy  Tags:Go语言   点击:(237)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条