Go语言:如何使用枚举类型?

brqmpdu1  于 2023-03-16  发布在  Go
关注(0)|答案(4)|浏览(119)

我有一个定义特定事件的事件列表(枚举):

package events

const (
    NEW_USER       = "NEW_USER"
    DIRECT_MESSAGE = "DIRECT_MESSAGE"
    DISCONNECT     = "DISCONNECT"
)

并且有一个结构体将使用这个枚举作为它的一个属性

type ConnectionPayload struct {
    EventName    string      `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}

是否有办法使用enum作为EventName的类型,而不是字符串?
这在typescript中是可能的,但不确定如何在golang中实现。

  • 我希望开发人员使用枚举强制使用正确的事件名称,而不是错误地使用任何随机字符串的eventname*。
vlf7wbxs

vlf7wbxs1#

go中目前还没有枚举类型,而且目前也没有一种直接的方法来强制执行与typescript相同的规则。
Go语言中常见的做法是使用@ttrasn发布的建议:
定义一个自定义类型,并使用您的“枚举”值定义类型常量:

type EventName string

const (
    NEW_USER       EventName = "NEW_USER"
    DIRECT_MESSAGE EventName = "DIRECT_MESSAGE"
    DISCONNECT     EventName = "DISCONNECT"
)

这就允许你在代码中标记出期望得到这样一个值的地方:

// example function signature :
func OnEvent(e EventName, id int) error { ... }

// example struct :
type ConnectionPayload struct {
    EventName    EventName  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}

并且它将阻止将普通string分配给EventName

var str string = "foo"
var ev EventName

ev = str // won't compile
OnEvent(str, 42) // won't compile

已知的局限性包括:

  • 在Go语言中,总是有一个零值:
var ev EventName  // ev is ""
  • 字符串文本与类型化变量不同,可以赋值:
var ev EventName = "SOMETHING_ELSE"
  • 允许铸造:
var str string = "foo"
var ev EventName = EventName(str)
  • 不检查解组:
jsn := []byte(`{"eventName":"SOMETHING_ELSE","eventPayload":"some message"}`)
err := json.Unmarshal(jsn, &payload) // no error

https://go.dev/play/p/vMUTpvH8DBb
如果您希望进行更严格的检查,则必须自己编写验证器或自定义解组拆收器。

2w2cym1i

2w2cym1i2#

您可以通过生成如下所示的代码来完成此操作。

type EventNames string

const (
    NEW_USER       EventNames = "NEW_USER"
    DIRECT_MESSAGE EventNames = "DIRECT_MESSAGE"
    DISCONNECT     EventNames = "DISCONNECT"
)

然后把你的结构改成这样

type ConnectionPayload struct {
    EventName    EventNames  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}
nuypyhwy

nuypyhwy3#

试试这个:

package main

import (
    "fmt"

    "gopkg.in/go-playground/validator.v9"
)

type EventName string

const (
    NEW_USER       EventName = "NEW_USER"
    DIRECT_MESSAGE EventName = "DIRECT_MESSAGE"
    DISCONNECT     EventName = "DISCONNECT"
)

type ConnectionPayload struct {
    EventName    EventName   `json:"eventName" validate:"oneof=NEW_USER DIRECT_MESSAGE DISCONNECT"`
    EventPayload interface{} `json:"eventPayload"`
}

func (s *ConnectionPayload) Validate() error {
    validate := validator.New()
    return validate.Struct(s)
}

func main() {
    a := ConnectionPayload{
        EventName: "NEW_USER",
    }
    b := ConnectionPayload{
        EventName: "NEW_USERR",
    }
    err := a.Validate()
    fmt.Println(a, err)
    err = b.Validate()
    fmt.Println(b, err)
}
r9f1avp5

r9f1avp54#

这可能有点超出范围,但我正在搜索问题底部提到的OP:
我希望开发人员强制使用正确的事件名称。
但在研究过程中,我发现强行密封并不是应该做的事情。

免责声明

在GO中,枚举类型不应该这样做。GO告诉我,当你编程时,你应该负责,即使你被允许创建枚举的新示例,你也不应该做什么。在这个特定的情况下,使用solution @ttrasn建议要好得多。
Go语言允许你通过创建一个公共的、可导出的接口类型来封装一个接口,这个接口是封装的,因为只有一个包可以实现它,而这个包就是声明接口的那个包。

type EventName interface {
    eventName_internal()
}

type eventName string
func (eventName) eventName_internal() {}

const (
    NEW_USER       eventName = "NEW_USER"
    DIRECT_MESSAGE eventName = "DIRECT_MESSAGE"
    DISCONNECT     eventName = "DISCONNECT"
)

但是,当你需要接受特定接口的有限实现集时,通常使用这种模式来代替枚举模式,尽管这是一个很好的例子来展示如何密封类型,并具有与其他编程语言相同的功能。

相关问题