小技巧分享:在 Go 如何實現列舉?

煎魚發表於2021-11-08

大家好,我是煎魚。

在日常的業務工程開發中,我們常常會有使用列舉值的訴求,列舉控的好,測試值邊界一遍過...

有的小夥伴會說,在 Go 語言不是有 iota 型別做列舉嗎,那煎魚你這篇文章還講什麼?

講道理,Go 語言並沒有 enum 關鍵字,有用過 Protobuf 等的小夥伴知道,Go 語言只是 ”有限的列舉“ 支援,我們也會用常量來定義,列舉值也需要有字面意思的對映。

示例

在一些業務場景下,是沒法達到我們的訴求的。示例如下:

type FishType int

const (
 A FishType = iota
 B
 C
 D
)

func main() {
 fmt.Println(A, B, C, D)
}

輸出結果為:“0 1 2 3”。這時候就一臉懵逼了...列舉值,應該除了鍵以外,還得有個對應的值。也就是這個 “0 1 2 3” 分別對應著什麼含義,是不是應該輸出 ”A B C D“

但 Go 語言這塊就沒有直接的支撐了,因此這不是一個完整的列舉型別的實現。

同時假設我們傳入超過 FishType 型別宣告範圍的列舉值,在 Go 語言中預設也不會有任何控制,是正常輸出的。

上述這種 Go 列舉實現,在某種情況下是不完全的,嚴格意義上不能成為 enum(列舉)。

使用 String 做列舉

如果要支援列舉值的對應輸出的話,我們可以通過如下方式:

type FishType int

const (
 A FishType = iota
 B
 C
 D
)

func (f FishType) String() string {
 return [...]string{"A", "B", "C", "D"}[f]
}

執行程式:

func main() {
 var f FishType = A
 fmt.Println(f)
 switch f {
 case A:
  fmt.Println("腦子進煎魚了")
 case B:
  fmt.Println("記得點贊")
 default:
  fmt.Println("別別別...")
 }
}

輸出結果:

A
腦子進煎魚了

我們可以藉助 Go 中 String 方法的預設約定,針對於定義了 String 方法的型別,預設輸出的時候會呼叫該方法。

這樣就可以達到獲得列舉值的同時,也能拿到其對映的字面意思。

自動生成 String

但每次手動編寫還是比較麻煩的。在這一塊,我們可以利用官方提供的 cmd/string 來快速實現。

我們安裝如下命令:

go install golang.org/x/tools/cmd/stringer

在所需列舉值上設定 go:generate 指令:

//go:generate stringer -type=FishType
type FishType int

在專案根目錄執行:

go generate ./...

會在根目錄生成 fishtype\_string.go 檔案:

.
├── fishtype_string.go
├── go.mod
├── go.sum
└── main.go

fishtype\_string 檔案內容:

package main

import "strconv"

const _FishType_name = "ABCD"

var _FishType_index = [...]uint8{0, 1, 2, 3, 4}

func (i FishType) String() string {
 if i < 0 || i >= FishType(len(_FishType_index)-1) {
  return "FishType(" + strconv.FormatInt(int64(i), 10) + ")"
 }
 return _FishType_name[_FishType_index[i]:_FishType_index[i+1]]
}

所生成出來的檔案,主要是根據列舉值和對映值做了個對映,且針對超出列舉值的場景進行了判斷:

func main() {
 var f1 FishType = A
 fmt.Println(f1)
 var f2 FishType = E
 fmt.Println(f2)
}

執行 go run . 檢視程式執行結果:

$ go run .
A
FishType(4)

總結

在今天這篇文章中,我們介紹瞭如何在 Go 語言實現標準的列舉值,雖然有些繁瑣,但整體不會太難。

也有小夥伴已經在社群中提出了 ”proposal: spec: add typed enum support“ 的提案,相信未來有機會能看到 Go 自身支援 enum(列舉)的那一天。

你平時會怎麼在業務程式碼中實現列舉呢,歡迎大家一起留言交流:)

歡迎大家在評論區留言和交流 :)

若有任何疑問歡迎評論區反饋和交流,最好的關係是互相成就,各位的點贊就是煎魚創作的最大動力,感謝支援。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

相關文章