在Go中表示枚举的惯用方法是什么?



我试图表示一个简化的染色体,它由N个碱基组成,每个碱基只能是{A, C, T, G}中的一个。

我想用枚举形式化约束,但我想知道在Go中模拟枚举的最惯用的方式是什么。

引自语言规范:Iota

在常量声明中,预先声明的标识符iota表示连续的未类型化整型常量。当保留字const出现在源中时,它被重置为0,并在每次ConstSpec之后递增。它可以用来构造一组相关的常量:

const (  // iota is reset to 0
        c0 = iota  // c0 == 0
        c1 = iota  // c1 == 1
        c2 = iota  // c2 == 2
)
const (
        a = 1 << iota  // a == 1 (iota has been reset)
        b = 1 << iota  // b == 2
        c = 1 << iota  // c == 4
)
const (
        u         = iota * 42  // u == 0     (untyped integer constant)
        v float64 = iota * 42  // v == 42.0  (float64 constant)
        w         = iota * 42  // w == 84    (untyped integer constant)
)
const x = iota  // x == 0 (iota has been reset)
const y = iota  // y == 0 (iota has been reset)

在ExpressionList中,每个iota的值是相同的,因为它只在每次ConstSpec之后递增:

const (
        bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0
        bit1, mask1                           // bit1 == 2, mask1 == 1
        _, _                                  // skips iota == 2
        bit3, mask3                           // bit3 == 8, mask3 == 7
)

最后一个例子利用了最后一个非空表达式列表的隐式重复。


所以你的代码可能像

const (
        A = iota
        C
        T
        G
)

type Base int
const (
        A Base = iota
        C
        T
        G
)

如果你想把base类型和int类型分开。

参考jnml的答案,您可以通过根本不导出Base类型(即小写)来防止Base类型的新实例。如果需要,您可以创建一个可导出的接口,该接口具有返回基类型的方法。该接口可用于处理base的外部函数,即

package a
type base int
const (
    A base = iota
    C
    T
    G
)

type Baser interface {
    Base() base
}
// every base must fulfill the Baser interface
func(b base) Base() base {
    return b
}

func(b base) OtherMethod()  {
}

package main
import "a"
// func from the outside that handles a.base via a.Baser
// since a.base is not exported, only exported bases that are created within package a may be used, like a.A, a.C, a.T. and a.G
func HandleBasers(b a.Baser) {
    base := b.Base()
    base.OtherMethod()
}

// func from the outside that returns a.A or a.C, depending of condition
func AorC(condition bool) a.Baser {
    if condition {
       return a.A
    }
    return a.C
}

在主包中a.Baser现在有效地像一个enum。只有在包内才能定义新的实例。

你可以让它如此:

type MessageType int32
const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

使用这段代码,编译器应该检查枚举的类型

上面使用constiota的例子确实是Go中表示基本枚举的最习惯的方式。但是,如果您正在寻找一种方法来创建一个功能更全面的enum,类似于您在Java或Python等其他语言中看到的类型,该怎么办呢?

在Python中创建一个看起来和感觉起来像字符串enum的对象的一个非常简单的方法是:

package main
import (
    "fmt"
)
var Colors = newColorRegistry()
func newColorRegistry() *colorRegistry {
    return &colorRegistry{
        Red:   "red",
        Green: "green",
        Blue:  "blue",
    }
}
type colorRegistry struct {
    Red   string
    Green string
    Blue  string
}
func main() {
    fmt.Println(Colors.Red)
}

假设您还需要一些实用方法,如Colors.List()Colors.Parse("red")。你的颜色更复杂,需要一个结构。然后你可以做一些像这样的事情:

package main
import (
    "errors"
    "fmt"
)
var Colors = newColorRegistry()
type Color struct {
    StringRepresentation string
    Hex                  string
}
func (c *Color) String() string {
    return c.StringRepresentation
}
func newColorRegistry() *colorRegistry {
    red := &Color{"red", "F00"}
    green := &Color{"green", "0F0"}
    blue := &Color{"blue", "00F"}
    return &colorRegistry{
        Red:    red,
        Green:  green,
        Blue:   blue,
        colors: []*Color{red, green, blue},
    }
}
type colorRegistry struct {
    Red   *Color
    Green *Color
    Blue  *Color
    colors []*Color
}
func (c *colorRegistry) List() []*Color {
    return c.colors
}
func (c *colorRegistry) Parse(s string) (*Color, error) {
    for _, color := range c.List() {
        if color.String() == s {
            return color, nil
        }
    }
    return nil, errors.New("couldn't find it")
}
func main() {
    fmt.Printf("%sn", Colors.List())
}

在这一点上,它当然工作,但你可能不喜欢你必须重复定义颜色。如果现在您想要消除它,您可以在struct上使用标记并做一些奇特的反射来设置它,但希望这足以覆盖大多数人。

有一种方法使用struct命名空间

的好处是所有枚举变量都在一个特定的命名空间下,以避免污染。问题是我们只能使用var而不能使用const

type OrderStatusType string
var OrderStatus = struct {
    APPROVED         OrderStatusType
    APPROVAL_PENDING OrderStatusType
    REJECTED         OrderStatusType
    REVISION_PENDING OrderStatusType
}{
    APPROVED:         "approved",
    APPROVAL_PENDING: "approval pending",
    REJECTED:         "rejected",
    REVISION_PENDING: "revision pending",
}

从Go 1.4开始,go generate工具和stringer命令一起被引入,这使得枚举易于调试和打印。

对于这样的用例,使用字符串常量可能会很有用,这样它就可以被封送到JSON字符串中。在下面的示例中,[]Base{A,C,G,T}将被封送到["adenine","cytosine","guanine","thymine"]

type Base string
const (
    A Base = "adenine"
    C      = "cytosine"
    G      = "guanine"
    T      = "thymine"
)

当使用iota时,值被封送成整数。在下面的示例中,[]Base{A,C,G,T}将被封送到[0,1,2,3]

type Base int
const (
    A Base = iota
    C
    G
    T
)

下面是比较两种方法的例子:

https://play.golang.org/p/VvkcWvv-Tvj

我相信我们这里有很多好的答案。但是,我只是想添加我使用枚举类型的方式

package main
import "fmt"
type Enum interface {
    name() string
    ordinal() int
    values() *[]string
}
type GenderType uint
const (
    MALE = iota
    FEMALE
)
var genderTypeStrings = []string{
    "MALE",
    "FEMALE",
}
func (gt GenderType) name() string {
    return genderTypeStrings[gt]
}
func (gt GenderType) ordinal() int {
    return int(gt)
}
func (gt GenderType) values() *[]string {
    return &genderTypeStrings
}
func main() {
    var ds GenderType = MALE
    fmt.Printf("The Gender is %sn", ds.name())
}

这是到目前为止我们可以在Go中创建和使用枚举类型的惯用方法之一。

编辑:

添加另一种使用常量枚举

的方法
package main
import (
    "fmt"
)
const (
    // UNSPECIFIED logs nothing
    UNSPECIFIED Level = iota // 0 :
    // TRACE logs everything
    TRACE // 1
    // INFO logs Info, Warnings and Errors
    INFO // 2
    // WARNING logs Warning and Errors
    WARNING // 3
    // ERROR just logs Errors
    ERROR // 4
)
// Level holds the log level.
type Level int
func SetLogLevel(level Level) {
    switch level {
    case TRACE:
        fmt.Println("trace")
        return
    case INFO:
        fmt.Println("info")
        return
    case WARNING:
        fmt.Println("warning")
        return
    case ERROR:
        fmt.Println("error")
        return
    default:
        fmt.Println("default")
        return
    }
}
func main() {
    SetLogLevel(INFO)
}

这是一个在有许多枚举时非常有用的示例。它使用Golang中的结构,并利用面向对象原则将它们捆绑在一起,形成一个整洁的小包。添加或删除新枚举时,底层代码都不会更改。流程如下:

  • 定义enumeration items的枚举结构:EnumItem。它有整数和字符串类型。
  • enumeration定义为enumeration items的列表:Enum
  • 枚举的构建方法。其中包括一些:
    • enum.Name(index int):返回给定索引的名称
    • enum.Index(name string):返回给定索引的名称
    • enum.Last():返回最后一个枚举
    • 的索引和名称
  • 添加枚举定义
下面是一些代码:
type EnumItem struct {
    index int
    name  string
}
type Enum struct {
    items []EnumItem
}
func (enum Enum) Name(findIndex int) string {
    for _, item := range enum.items {
        if item.index == findIndex {
            return item.name
        }
    }
    return "ID not found"
}
func (enum Enum) Index(findName string) int {
    for idx, item := range enum.items {
        if findName == item.name {
            return idx
        }
    }
    return -1
}
func (enum Enum) Last() (int, string) {
    n := len(enum.items)
    return n - 1, enum.items[n-1].name
}
var AgentTypes = Enum{[]EnumItem{{0, "StaffMember"}, {1, "Organization"}, {1, "Automated"}}}
var AccountTypes = Enum{[]EnumItem{{0, "Basic"}, {1, "Advanced"}}}
var FlagTypes = Enum{[]EnumItem{{0, "Custom"}, {1, "System"}}}

重构https://stackoverflow.com/a/17989915/863651,使其更具可读性:

package SampleEnum
type EFoo int
const (
    A EFoo = iota
    C
    T
    G
)
type IEFoo interface {
    Get() EFoo
}
func(e EFoo) Get() EFoo { // every EFoo must fulfill the IEFoo interface
    return e
}
func(e EFoo) otherMethod()  { // "private"
    //some logic
}

这是在golang中实现enum的一种安全方式:

package main
import (
    "fmt"
)
const (
    MALE   = _gender(1)
    FEMALE = _gender(2)
    RED    = _color("RED")
    GREEN  = _color("GREEN")
    BLUE   = _color("BLUE")
)
type Gender interface {
    _isGender()
    Value() int
}
type _gender int
func (_gender) _isGender() {}
func (_g _gender) Value() int {
    return int(_g)
}
type Color interface {
    _isColor()
    Value() string
}
type _color string
func (_color) _isColor() {}
func (_c _color) Value() string {
    return string(_c)
}
func main() {
    genders := []Gender{MALE, FEMALE}
    colors := []Color{RED, GREEN, BLUE}
    fmt.Println("Colors =", colors)
    fmt.Println("Genders =", genders)
}
输出:

Colors = [RED GREEN BLUE]
Genders = [1 2]

此外,这是一种非常有效的方式来存储不同的角色在一个字节的一个位置,其中第一个值被设置为1,位移动一个iota。

package main
import "fmt"
const (
    isCaptain = 1 << iota
    isTrooper
    isMedic
    canFlyMars
    canFlyJupiter
    canFlyMoon
)
func main() {
    var roles byte = isCaptain | isMedic | canFlyJupiter
    //Prints a binary representation.
    fmt.Printf("%bn", roles)
    fmt.Printf("%bn", isCaptain)
    fmt.Printf("%bn", isTrooper)
    fmt.Printf("%bn", isMedic)
    fmt.Printf("Is Captain? %vn", isCaptain&roles == isCaptain)
    fmt.Printf("Is Trooper? %v", isTrooper&roles == isTrooper)
}

我以这种方式创建了enum。假设我们需要一个表示性别的enum。取值包括:Male、Female、Others

package gender
import (
    "fmt"
    "strings"
)
type Gender struct {
    g string
}
var (
    Unknown = Gender{}
    Male    = Gender{g: "male"}
    Female  = Gender{g: "female"}
    Other   = Gender{g: "other"}
)
var genders = []Gender{
    Unknown,
    Male,
    Female,
    Other,
}
func Parse(code string) (parsed Gender, err error) {
    for _, g := range genders {
        if g.g == strings.ToLower(code) {
            if g == Unknown {
                err = fmt.Errorf("unknown gender")
            }
            parsed = g
            return
        }
    }
    parsed = Unknown
    err = fmt.Errorf("unknown gender", code)
    return
}
func (g Gender) Gender() string {
    return g.g
}

我发现了一种更简单的方法。

const (
Stake TX = iota
Withdraw)

type TX int
func (t TX) String() string {
return [...]string{"STAKE", "WITHDRAW"}[t]}
log.Println(Stake.String()) --> STAKE

相关内容

最新更新