当前位置:首页 > 科技  > 软件

Go 语言中 enum 实现方式有哪些?一定要绝对类型安全吗?

来源: 责编: 时间:2024-02-02 17:01:25 352观看
导读嗨!大家好,本文 Go 语言小技巧系列的第十二篇,往期文章查看:Go 语言小技巧。你是否了解过 Go 中的枚举呢?枚举,即 enum,可用于表示一组范围固定的值,它能助我们写出清晰、安全的代码。以编写游戏程序为一个简单案例:游戏中的角

嗨!大家好,本文 Go 语言小技巧系列的第十二篇,往期文章查看:Go 语言小技巧。GKf28资讯网——每日最新资讯28at.com

你是否了解过 Go 中的枚举呢?GKf28资讯网——每日最新资讯28at.com

枚举,即 enum,可用于表示一组范围固定的值,它能助我们写出清晰、安全的代码。GKf28资讯网——每日最新资讯28at.com

以编写游戏程序为一个简单案例:游戏中的角色有如战士、法师或者弓箭手,这种范围固定的值,就可以用枚举来表示。GKf28资讯网——每日最新资讯28at.com

但 Go 中,枚举的表现方式不像在某些其他语言中那样直接。我们要想在 Go 中用好枚举,就要了解 Go 中枚举的不同表示形式和使用注意点。GKf28资讯网——每日最新资讯28at.com

使用 iota 和常量

在 Go 中,使用 iota 和常量是最常见的表示枚举的方式。GKf28资讯网——每日最新资讯28at.com

什么是 iota?GKf28资讯网——每日最新资讯28at.com

iota 是 Go 中是一个非常特别的 Keyword,它可以帮助我们按一定规则创建一系列相关的常量,而无需手动为每个变量单独赋值。这一点与枚举的用途天然契合。GKf28资讯网——每日最新资讯28at.com

不了解上面文字的含义?GKf28资讯网——每日最新资讯28at.com

让我们来看一个例子,基于 iota 快速创建特定规则的常量。GKf28资讯网——每日最新资讯28at.com

示例代码,如下所示:GKf28资讯网——每日最新资讯28at.com

type Weekday intconst (    Sunday    Weekday = iota // 0    Monday                   // 1    Tuesday                  // 2    Wednesday                // 3    Thursday                 // 4    Friday                   // 5    Saturday                 // 6)

例子中,Weekday 类型有 7 个值,分别代表一周的七天。内部值从 0 开始,iota 自动增加赋值给每个常量,从 Sunday 到 Saturday 分别赋值 0-6。GKf28资讯网——每日最新资讯28at.com

图片图片GKf28资讯网——每日最新资讯28at.com

现在,我们就不用手动给每个常量赋值。GKf28资讯网——每日最新资讯28at.com

iota 还有很多骚操作,本文目标不在此,就不展开了。GKf28资讯网——每日最新资讯28at.com

这种方法的优点是简单,提供了一定程度上类型安全,但它也有局限性。GKf28资讯网——每日最新资讯28at.com

我觉得主要是两点不足。GKf28资讯网——每日最新资讯28at.com

首先,对比其他语言的枚举,它不能直接从字符串转换到枚举类型,以上面代码为例,它不能从 "Sunday" 字符串转为 Sunday 枚举值。GKf28资讯网——每日最新资讯28at.com

其次,它的类型安全不是绝对安全。GKf28资讯网——每日最新资讯28at.com

如上的 Weekday 类型,我们虽不能将一个明确类型的变量赋值给 Weekday 类型变量:GKf28资讯网——每日最新资讯28at.com

day := 0 // int// compiler: cannot use day (variable of type int) // as Weekday value in variable declaration [IncompatibleAssign]var sunday Weekday = day

但却可以将一个非 Weekday 类的字面量赋值给它。GKf28资讯网——每日最新资讯28at.com

// 字面量 10 赋值给类型为 Weekday 的 day 变量var day Weekday = 10

很明显,10 这个数字并不在 Weekday 的有效范围,但却可以有效赋值而并没有报错。GKf28资讯网——每日最新资讯28at.com

图片图片GKf28资讯网——每日最新资讯28at.com

如果是其他枚举机制完善的 enum 类型的语言,肯定是无法编译通过的。GKf28资讯网——每日最新资讯28at.com

除了最基础的实现方式,我们继续看看还有哪些其他表示形式吧。GKf28资讯网——每日最新资讯28at.com

支持字符串转化的枚举值

我们在开发 Web 应用时,常会遇到要将枚举值以字符串形式表示的需求,特别是在与前端对接时。那么,就让我们先尝试实现这一个需求,string 变量与枚举变量相互转化。GKf28资讯网——每日最新资讯28at.com

图片图片GKf28资讯网——每日最新资讯28at.com

这个问题说来简单,Go 语言中,我们可采用字符串常量作为枚举值。GKf28资讯网——每日最新资讯28at.com

示例代码,如下所示:GKf28资讯网——每日最新资讯28at.com

type HttpMethod stringconst (    Get    HttpMethod = "GET"    Post   HttpMethod = "POST"    Put    HttpMethod = "PUT"    Delete HttpMethod = "DELETE")

这种方法简单直观,而且也易于与 JSON 等数据格式转换。GKf28资讯网——每日最新资讯28at.com

type Request struct {    Method HttpMethod    URL    string}func main() {    r := Request{Method: Get, URL: "https://zhihu.com"}    result, _ := json.Marshal(r)    fmt.Printf("%s/n", result)}

输出:GKf28资讯网——每日最新资讯28at.com

{"Method":"GET","URL":"https://zhihu.com"}

当如果我们还想保留原始的 iota 的整型枚举值,毕竟它更轻量,占用内存空少。这是否可以实现呢?我们尝试一下吧。GKf28资讯网——每日最新资讯28at.com

定义如下:GKf28资讯网——每日最新资讯28at.com

type HttpMethod intconst (    Get    HttpMethod = iota    Post    Put    Delete)

只要在枚举类型上增加整型值与字符串两者间相互转化的方法即可。GKf28资讯网——每日最新资讯28at.com

代码如下所示:GKf28资讯网——每日最新资讯28at.com

// 从 string 转为 HttpMethodfunc NewFromString(method string) HttpMethod {  switch h {  case "Get":    // 省略 ...  case "Delete":  default:    return Get // default is Get or error, panic  }}// 从 HttpMethod 转为 stringfunc (h HttpMethod) String() string {  switch h {  case Get:    return "Get"    // 省略 ...  default:    return "Unknown" // or error, panic  }}

我们实现从 string 构造 enum 方法,和从 enum 类型转为 string 的 String 方法。GKf28资讯网——每日最新资讯28at.com

这里存在的一个问题,如果希望支持友好的 JSON 序列化反序列化的话,即枚举用字符串表示,则需要为 HttpMethod 新增方法,实现 json.Marshaler和json.Unmarshaler接口,自定义这个转化过程。GKf28资讯网——每日最新资讯28at.com

图片图片GKf28资讯网——每日最新资讯28at.com

代码如下:GKf28资讯网——每日最新资讯28at.com

// MarshalJSON 实现 json.Marshaler 接口func (h HttpMethod) MarshalJSON() ([]byte, error) {    return json.Marshal(h.String())}// UnmarshalJSON 实现 json.Unmarshaler 接口func (h *HttpMethod) UnmarshalJSON(data []byte) error {    var method string    if err := json.Unmarshal(data, &method); err != nil {        return err    }    *h = NewFromString(method)    return nil}

如果去找一些开源项目,可能会发现一些实现了这种 enum 的包,你只要通过 iota 定义枚举类型,从字符串和枚举间转化的代码可通过命令直接生成。GKf28资讯网——每日最新资讯28at.com

robpike 开发过一个工具名为 stringer[1],可直接基于类似如上 HttpMethod 定义生成 String() 方法,不过它不是完整的 enum 支持。GKf28资讯网——每日最新资讯28at.com

//go:generate stringer -type=HttpMethodtype HttpMethod intconst (    Get    HttpMethod = iota    Post    Put    Delete)

我们执行 go generate 即可为 HttpMethod 类型生成 String 方法。GKf28资讯网——每日最新资讯28at.com

go generate

这里有个提前,要单独安装下 stringer 命令。GKf28资讯网——每日最新资讯28at.com

不过,即使到现在,依然存在类型安全的问题,类似 var Hello HttpMethod = 10 这样的代码依然有效。GKf28资讯网——每日最新资讯28at.com

继续吧!GKf28资讯网——每日最新资讯28at.com

结构体枚举值

GO 中可基于结构体类型,实现枚举效果。GKf28资讯网——每日最新资讯28at.com

举例说明,我们创建一个颜色的枚举,要求不仅有颜色的名字,还有颜色的 RGB 值。同时,为了方便记录,我们可以给它加上一个枚举整型值。GKf28资讯网——每日最新资讯28at.com

type Color struct {    Index int    Name  string    RGB   string}

这样我们就有了一个颜色的枚举,每个颜色都有一个索引、名字和 RGB 值。GKf28资讯网——每日最新资讯28at.com

如何使用呢?GKf28资讯网——每日最新资讯28at.com

类似于前面的方式,我们直接定义,如下所示:GKf28资讯网——每日最新资讯28at.com

var (      Red   = Color{0, "Red", "#FF0000"}      Green = Color{1, "Green", "#00FF00"}      Blue  = Color{2, "Blue", "#0000FF"})

这种方法比较灵活,但也相对复杂。GKf28资讯网——每日最新资讯28at.com

好处也比较明显,如现在能存储的信息也更加丰富,前面类似于整型与字符串的各种转化都变的轻而易举了。我们直接整型数值 Color.Index、字符串 Color.Name。GKf28资讯网——每日最新资讯28at.com

不过,如果要最大化与其他库结合,如自定义 JSON 转化规则,要实现 JSON 序列和反序列接口,字符串格式化要实现 Stringer 接口等。GKf28资讯网——每日最新资讯28at.com

还有,这种结构其实不是常量类型的,就存在数据可更改的问题。不过,有这个安全需求的话,可考虑将成员字段私有化,通过方法变更即可。GKf28资讯网——每日最新资讯28at.com

结构体类似命名空间效果

在网上看到个有点傻的设计,顺便也提一下吧。GKf28资讯网——每日最新资讯28at.com

假设,我们有很多枚举类型,担心可能会出现命名冲突,可以用结构体来创建一个“命名空间”,把相关的枚举值组织在一起:GKf28资讯网——每日最新资讯28at.com

示例代码如下所示:GKf28资讯网——每日最新资讯28at.com

var Colors = struct {    Red, Green, Blue Color}{      Red   = Color{0, "Red", "#FF0000"}      Green = Color{1, "Green", "#00FF00"}      Blue  = Color{2, "Blue", "#0000FF"}}

上面的例子中定义了 Colors 变量,它是匿名结构体类型,字段名表示颜色,我们可通过 Colors.xxx 形式调用颜色。GKf28资讯网——每日最新资讯28at.com

我初期看到这个写法,还以为限定了类型可定义的枚举值范围。发现其实不是,我依然可使用 Color 类型定义新值。GKf28资讯网——每日最新资讯28at.com

这很不优雅,也很鸡肋,其实我完全可以新建 package 实现。不过既然发现了这个方案,就写到这里吧。GKf28资讯网——每日最新资讯28at.com

类型安全?

到这里,其实所有实现方式都没有解决一个问题,那就是定义完枚举后,依然继续添加新的枚举值。GKf28资讯网——每日最新资讯28at.com

我真的想实现这样的能力呢?该如何做呢?GKf28资讯网——每日最新资讯28at.com

以前面 HttpMethod 为例,我要做的就是禁止通过 HttpMethod(1) 创建新枚举值。GKf28资讯网——每日最新资讯28at.com

这不是很简单吗?GKf28资讯网——每日最新资讯28at.com

图片图片GKf28资讯网——每日最新资讯28at.com

我们只要将枚举实现封装成一个 package,将类型小写,如 httpMethod,暴露它的类似 FromString 和其它函数,实现强制通过转化函数它。GKf28资讯网——每日最新资讯28at.com

package httpmethodtype httpmethod stringconst (  Get  = "Get"  Post = "Post")func FromString(method string) httpmethod {  switch method {  case "Get":    return Get  case "Post":    return Post  }}

现在,枚举创建必须通过方法,我们就可以在其中实现限定创建规则。GKf28资讯网——每日最新资讯28at.com

方法可能挺好,但好像没见到这么玩的?GKf28资讯网——每日最新资讯28at.com

为什么呢?GKf28资讯网——每日最新资讯28at.com

我的猜想是,开发时我们不会随意创建新的枚举值,对于边界数据的传递,确保通过转化函数处理不就行了吗?GKf28资讯网——每日最新资讯28at.com

真实场景

对真实场景下枚举的使用,有价值之处主要在与其他系统的对接。GKf28资讯网——每日最新资讯28at.com

图片图片GKf28资讯网——每日最新资讯28at.com

举例而言,如来自前端 API 或数据库,有时可能出现一些异常值。对这类场景,通过前面介绍,可提供转化函数,在其中设置检查规则。如果发现异常选择丢弃,执行如 error 或 panic。GKf28资讯网——每日最新资讯28at.com

而对于内部系统,如果使用类似于 protobuffer 协议,可在协议上限定好枚举范围,标记异常数据等。GKf28资讯网——每日最新资讯28at.com

当然,可能出现因为发布时间次序或者兄弟团队忘记通知等,导致系统间枚举值对不齐的情况,也会按上面的逻辑丢弃、error 等,便于即使发现。GKf28资讯网——每日最新资讯28at.com

对于团队合作这类场景,最好的解决方式,还是要在设计系统时,考虑上下游的兼容性,而不是每当有变动,全员乱糟糟,这最容易导致生产事故了。GKf28资讯网——每日最新资讯28at.com

其实无论哪一种情况,重点在于保证进入系统的数据是否可通过转化检测,而不是多此一举,限制类似于 HttpMethod("Get") 的类型转化,因为没有人会这么写代码。GKf28资讯网——每日最新资讯28at.com

总结

Go 语言中,枚举的表达方式多种多样。从简单的 iota 到复杂的结构体方式,每种方法都有其适用场景。作为开发者,最好是根据自己的具体需求,选择合适的实现方式。GKf28资讯网——每日最新资讯28at.com

最后,希望这篇文章能帮助你在使用 Go 语言时,更加灵活且游刃有余地使用枚举 enum。GKf28资讯网——每日最新资讯28at.com

博客地址:Go语言中 enum 实现方式有哪些?一定要类型安全吗?[2]GKf28资讯网——每日最新资讯28at.com

引用链接

[1] stringer: https://pkg.go.dev/golang.org/x/tools/cmd/stringerGKf28资讯网——每日最新资讯28at.com

[2] Go语言中 enum 实现方式有哪些?一定要类型安全吗?: https://www.poloxue.com/posts/2024-02-02-how-to-use-enums-type-in-golang/GKf28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-71945-0.htmlGo 语言中 enum 实现方式有哪些?一定要绝对类型安全吗?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Nodejs - 九步开启JWT身份验证

下一篇: 欧盟反垄断「撬开」苹果,结果坑苦了开发者

标签:
  • 热门焦点
  • 天猫精灵Sound Pro体验:智能音箱没有音质?来听听我的

    这几年除了手机作为智能生活终端最主要的核心之外,第二个可以成为中心点的产品是什么?——是智能音箱。 手机在执行命令的时候有两种操作方式,手和智能语音助手,而智能音箱只
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • 摸鱼心法第一章——和配置文件说拜拜

    为了能摸鱼我们团队做了容器化,但是带来的问题是服务配置文件很麻烦,然后大家在群里进行了“亲切友好”的沟通图片图片图片图片对比就对比,简单对比下独立配置中心和k8s作为配
  • 学习JavaScript的10个理由...

    作者 | Simplilearn编译 | 王瑞平当你决心学习一门语言的时候,很难选择到底应该学习哪一门,常用的语言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-
  • 慕岩炮轰抖音,百合网今何在?

    来源:价值研究所 作者:Hernanderz“难道就因为自己的一个产品牛逼了,从客服到总裁,都不愿意正视自己产品和运营上的问题,选择逃避了吗?”这一番话,出自百合网联合创
  • 中国家电海外掘金正当时|出海专题

    作者|吴南南编辑|胡展嘉运营|陈佳慧出品|零态LT(ID:LingTai_LT)2023年,出海市场战况空前,中国创业者在海外纷纷摩拳擦掌,以期能够把中国的商业模式、创业理念、战略打法输出海外,他们依
  • DRAM存储器10月价格下跌,NAND闪存本月价格与上月持平

    10月30日,据韩国媒体消息,自今年年初以来一直在上涨的 DRAM 存储器的交易价格仅在本月就下跌了近 10%,此次是全年首次降价,而NAND 闪存本月价格与上月持平。市
  • 华为举行春季智慧办公新品发布会 首次推出电子墨水屏平板

    北京时间2月27日晚,华为在巴塞罗那举行春季智慧办公新品发布会,在海外市场推出之前已经在中国市场上市的笔记本、平板、激光打印机等办公产品,并首次推出搭载
  • AI艺术欣赏体验会在上海梅赛德斯奔驰中心音乐俱乐部上演

    光影交错的镜像世界,虚实幻化的视觉奇观,虚拟偶像与真人共同主持,这些场景都出现在2019世界人工智能大会的舞台上。8月29日至31日,“AI艺术欣赏体验会”在上海
Top