Go 如何实现“真正意义上”的枚举

枚举是一个强大的特性,拥有广泛的应用场景。然而,在 Go 语言中,却没有原生支持枚举。在本文中,我将用 Go 语言的实现枚举。

什么是枚举?

枚举是从字面含义来说,就是列举出有穷集合中的所有元素。像一周的表示就是一个有限集合,只有七种可能的取值,所以一周就可以视为一个枚举类型。一个枚举项包含一个索引值(key)和一个字符值(value)。

Key 1 2 3 4 5 6 7
Enum Sunday Monday Tuesday Wednesday Thursday Friday Saturday
Value “Sunday” “Monday” “Tuesday” “Wednesday” “Thursday” “Friday” “Saturday”

使用Go实现枚举?

枚举基本实现

先来实现基本的枚举功能,我们要实现以下几点:

  • 通过 type 关键字给 int 类型起别名,达到自定义枚举类型的效果;
  • 通过 iota 关键字 + 自定义的类型赋值给常量,作为枚举项;
  • 实现 Index 方法返回枚举项的索引值,借助类型转换,将自定义类型转回 int 类型;
  • 实现 String 方法返回枚举项的字符值,Go 语言 Stringer 接口定义 String 方法,一个类型只要有 String 方法,我们就说它实现了 Stringer 接口,那么在格式化时就会调用 String 方法处理;

Stringer接口的定义如下:

type Stringer interface {
    String() string
}

以实现“周”的枚举为例,具体代码实现如下:

package Weekday

// Weekday 自定义类型
type Weekday int

// 声明每个枚举项的索引值
const (
    Sunday    Weekday = iota + 1 // Index = 1
    Monday                       // Index = 2
    Tuesday                      // Index = 3
    Wednesday                    // Index = 4
    Thursday                     // Index = 5
    Friday                       // Index = 6
    Saturday                     // Index = 7
)

var weekdayStr = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

// String - 返回枚举项的索引值
func (w Weekday) String() string {
    return weekdayStr[w-1]
}

// Index - 返回枚举项的字符值
func (w Weekday) Index() int {
    return int(w)
}

那该如何使用?我们可以这样使用枚举。

func main() {
    var sunday = Weekday.Sunday
    fmt.Println(sunday)          // Print : Sunday
    fmt.Println(sunday.String()) // Print : Sunday
    fmt.Println(sunday.Index())  // Print : 1
}

实现两个辅助函数

上面我们实现了枚举的基本功能,每个枚举值都有索引值,都有字符值。下面,为了让枚举更好用,我们在包内增加两个辅助函数:

  • Values 函数返回枚举的所有值;
  • ExistOf 函数判断某值是否存在枚举值中;

实现代码如下:

package Weekday

......

// Values 返回枚举的所有值
func Values() []string {
    return weekdayStr
}

// ExistOf 判断某值是否存在枚举值中
func ExistOf(str string) bool {
    for _, v := range weekdayStr {
        if v == str {
            return true
        }
    }
    return false
}

那该如何使用?我们可以这样使用这两个辅助函数。开发中常常会遇到判断入参是否合法的枚举值的场景,我们可以快速地使用 ExistOf 函数来实现应对。

func main() {
    var sunday = Weekday.Sunday

    fmt.Println(Weekday.Values())
    // Print : [Sunday Monday Tuesday Wednesday Thursday Friday Saturday]
    fmt.Println(Weekday.ExistOf("abc"))    // Print : false
    fmt.Println(Weekday.ExistOf("Monday")) // Print : true
}

完整代码

github.com/newbugcoder/learngo/tre...

package Weekday

// Weekday 自定义类型
type Weekday int

// 声明每个枚举项的索引值
const (
    Sunday    Weekday = iota + 1 // Index = 1
    Monday                       // Index = 2
    Tuesday                      // Index = 3
    Wednesday                    // Index = 4
    Thursday                     // Index = 5
    Friday                       // Index = 6
    Saturday                     // Index = 7
)

var weekdayStr = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

// ======== 枚举实现 ========

// String - 返回枚举项的索引值
func (w Weekday) String() string {
    return weekdayStr[w-1]
}

// Index - 返回枚举项的字符值
func (w Weekday) Index() int {
    return int(w)
}

// ======== 辅助函数 ========

// Values 返回枚举的所有值
func Values() []string {
    return weekdayStr
}

// ExistOf 判断某值是否存在枚举值中
func ExistOf(str string) bool {
    for _, v := range weekdayStr {
        if v == str {
            return true
        }
    }
    return false
}

总结

本文我们先介绍了枚举的结构,然后使用 Go 语言实现了枚举的特性以及辅助函数。本文的实现不能算得上完美,有点瑕疵,即:一个包作为一个枚举,包的利用率不高。但使用上算是相对优雅,也提供Go语言实现枚举的一种思路,如果有更好的方案,欢迎探讨。

go
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 3

:joy:你写个const呀,写个var不怕别人代码里篡改map么

2年前 评论
newbugcoder (楼主) 1年前

weekdayStr 感觉用 map 数据类型会更优雅

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!