欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言中實(shí)現(xiàn)enum枚舉的方法詳解

 更新時(shí)間:2024年02月02日 08:11:31   作者:波羅學(xué)  
枚舉,即?enum,可用于表示一組范圍固定的值,它能助我們寫出清晰、安全的代碼,那么你是否了解過?Go?中的枚舉呢?下面就跟隨小編一起來學(xué)習(xí)一下Go語言中實(shí)現(xiàn)enum枚舉的常用方法吧

你是否了解過 Go 中的枚舉呢?

枚舉,即 enum,可用于表示一組范圍固定的值,它能助我們寫出清晰、安全的代碼。

以編寫游戲程序?yàn)橐粋€(gè)簡單案例:游戲中的角色有如戰(zhàn)士、法師或者弓箭手,這種范圍固定的值,就可以用枚舉來表示。

但 Go 中,枚舉的表現(xiàn)方式不像在某些其他語言中那樣直接。我們要想在 Go 中用好枚舉,就要了解 Go 中枚舉的不同表示形式和使用注意點(diǎn)。

本文將以 Go 語言中如何使用枚舉為主題,從最簡單到復(fù)雜逐一介紹常見的方案。

使用 iota 和常量

在 Go 中,使用 iota 和常量是最常見的表示枚舉的方式。

什么是 iota?

iota 是 Go 中是一個(gè)非常特別的 Keyword,它可以幫助我們按一定規(guī)則創(chuàng)建一系列相關(guān)的常量,而無需手動(dòng)為每個(gè)變量單獨(dú)賦值。這一點(diǎn)與枚舉的用途天然契合。

不了解上面文字的含義?

讓我們來看一個(gè)例子,基于 iota 快速創(chuàng)建特定規(guī)則的常量。

示例代碼,如下所示:

type Weekday int

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

例子中,Weekday 類型有 7 個(gè)值,分別代表一周的七天。內(nèi)部值從 0 開始,iota 自動(dòng)增加賦值給每個(gè)常量,從 Sunday 到 Saturday 分別賦值 0-6。

現(xiàn)在,我們就不用手動(dòng)給每個(gè)常量賦值。

iota 還有很多騷操作,本文目標(biāo)不在此,就不展開了。

這種方法的優(yōu)點(diǎn)是簡單,提供了一定程度上類型安全,但它也有局限性。

我覺得主要是兩點(diǎn)不足。

首先,對比其他語言的枚舉,它不能直接從字符串轉(zhuǎn)換到枚舉類型,以上面代碼為例,它不能從 "Sunday" 字符串轉(zhuǎn)為 Sunday 枚舉值。

其次,它的類型安全不是絕對安全。

如上的 Weekday 類型,我們雖不能將一個(gè)明確類型的變量賦值給 Weekday 類型變量:

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

但卻可以將一個(gè)非 Weekday 類的字面量賦值給它。

// 字面量 10 賦值給類型為 Weekday 的 day 變量
var day Weekday = 10 

很明顯,10 這個(gè)數(shù)字并不在 Weekday 的有效范圍,但卻可以有效賦值而并沒有報(bào)錯(cuò)。

如果是其他枚舉機(jī)制完善的 enum 類型的語言,肯定是無法編譯通過的。

除了最基礎(chǔ)的實(shí)現(xiàn)方式,我們繼續(xù)看看還有哪些其他表示形式吧。

支持字符串轉(zhuǎn)化的枚舉值

我們在開發(fā) Web 應(yīng)用時(shí),常會(huì)遇到要將枚舉值以字符串形式表示的需求,特別是在與前端對接時(shí)。那么,就讓我們先嘗試實(shí)現(xiàn)這一個(gè)需求,string 變量與枚舉變量相互轉(zhuǎn)化。

這個(gè)問題說來簡單,Go 語言中,我們可采用字符串常量作為枚舉值。

示例代碼,如下所示:

type HttpMethod string

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

這種方法簡單直觀,而且也易于與 JSON 等數(shù)據(jù)格式轉(zhuǎn)換。

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)
}

輸出:

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

當(dāng)如果我們還想保留原始的 iota 的整型枚舉值,畢竟它更輕量,占用內(nèi)存空少。這是否可以實(shí)現(xiàn)呢?我們嘗試一下吧。

定義如下:

type HttpMethod int

const (
    Get    HttpMethod = iota
    Post
    Put
    Delete
)

只要在枚舉類型上增加整型值與字符串兩者間相互轉(zhuǎn)化的方法即可。

代碼如下所示:

// 從 string 轉(zhuǎn)為 HttpMethod
func NewFromString(method string) HttpMethod {
  switch h {
  case "Get":
    // 省略 ...
  case "Delete":
  default:
    return Get // default is Get or error, panic
  }
}

// 從 HttpMethod 轉(zhuǎn)為 string
func (h HttpMethod) String() string {
  switch h {
  case Get:
    return "Get"
    // 省略 ...
  default:
    return "Unknown" // or error, panic
  }
}

我們實(shí)現(xiàn)從 string 構(gòu)造 enum 方法,和從 enum 類型轉(zhuǎn)為 string 的 String 方法。

這里存在的一個(gè)問題,如果希望支持友好的 JSON 序列化反序列化的話,即枚舉用字符串表示,則需要為 HttpMethod 新增方法,實(shí)現(xiàn) json.Marshaler和json.Unmarshaler接口,自定義這個(gè)轉(zhuǎn)化過程。

代碼如下:

// MarshalJSON 實(shí)現(xiàn) json.Marshaler 接口
func (h HttpMethod) MarshalJSON() ([]byte, error) {
    return json.Marshal(h.String())
}

// UnmarshalJSON 實(shí)現(xiàn) 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
}

如果去找一些開源項(xiàng)目,可能會(huì)發(fā)現(xiàn)一些實(shí)現(xiàn)了這種 enum 的包,你只要通過 iota 定義枚舉類型,從字符串和枚舉間轉(zhuǎn)化的代碼可通過命令直接生成。

robpike 開發(fā)過一個(gè)工具名為 stringer[1],可直接基于類似如上 HttpMethod 定義生成 String() 方法,不過它不是完整的 enum 支持。

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

const (
    Get    HttpMethod = iota
    Post
    Put
    Delete
)

我們執(zhí)行 go generate 即可為 HttpMethod 類型生成 String 方法。

go generate

這里有個(gè)提前,要單獨(dú)安裝下 stringer 命令。

不過,即使到現(xiàn)在,依然存在類型安全的問題,類似 var Hello HttpMethod = 10 這樣的代碼依然有效。

繼續(xù)吧!

結(jié)構(gòu)體枚舉值

GO 中可基于結(jié)構(gòu)體類型,實(shí)現(xiàn)枚舉效果。

舉例說明,我們創(chuàng)建一個(gè)顏色的枚舉,要求不僅有顏色的名字,還有顏色的 RGB 值。同時(shí),為了方便記錄,我們可以給它加上一個(gè)枚舉整型值。

type Color struct {
    Index int
    Name  string
    RGB   string
}

這樣我們就有了一個(gè)顏色的枚舉,每個(gè)顏色都有一個(gè)索引、名字和 RGB 值。

如何使用呢?

類似于前面的方式,我們直接定義,如下所示:

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

這種方法比較靈活,但也相對復(fù)雜。

好處也比較明顯,如現(xiàn)在能存儲(chǔ)的信息也更加豐富,前面類似于整型與字符串的各種轉(zhuǎn)化都變的輕而易舉了。我們直接整型數(shù)值 Color.Index、字符串 Color.Name

不過,如果要最大化與其他庫結(jié)合,如自定義 JSON 轉(zhuǎn)化規(guī)則,要實(shí)現(xiàn) JSON 序列和反序列接口,字符串格式化要實(shí)現(xiàn) Stringer 接口等。

還有,這種結(jié)構(gòu)其實(shí)不是常量類型的,就存在數(shù)據(jù)可更改的問題。不過,有這個(gè)安全需求的話,可考慮將成員字段私有化,通過方法變更即可。

結(jié)構(gòu)體類似命名空間效果

在網(wǎng)上看到個(gè)有點(diǎn)傻的設(shè)計(jì),順便也提一下吧。

假設(shè),我們有很多枚舉類型,擔(dān)心可能會(huì)出現(xiàn)命名沖突,可以用結(jié)構(gòu)體來創(chuàng)建一個(gè)“命名空間”,把相關(guān)的枚舉值組織在一起:

示例代碼如下所示:

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

上面的例子中定義了 Colors 變量,它是匿名結(jié)構(gòu)體類型,字段名表示顏色,我們可通過 Colors.xxx 形式調(diào)用顏色。

我初期看到這個(gè)寫法,還以為限定了類型可定義的枚舉值范圍。發(fā)現(xiàn)其實(shí)不是,我依然可使用 Color 類型定義新值。

這很不優(yōu)雅,也很雞肋,其實(shí)我完全可以新建 package 實(shí)現(xiàn)。不過既然發(fā)現(xiàn)了這個(gè)方案,就寫到這里吧。

類型安全

到這里,其實(shí)所有實(shí)現(xiàn)方式都沒有解決一個(gè)問題,那就是定義完枚舉后,依然繼續(xù)添加新的枚舉值。

我真的想實(shí)現(xiàn)這樣的能力呢?該如何做呢?

以前面 HttpMethod 為例,我要做的就是禁止通過 HttpMethod(1) 創(chuàng)建新枚舉值。

這不是很簡單嗎?

我們只要將枚舉實(shí)現(xiàn)封裝成一個(gè) package,將類型小寫,如 httpMethod,暴露它的類似 FromString 和其它函數(shù),實(shí)現(xiàn)強(qiáng)制通過轉(zhuǎn)化函數(shù)它。

package httpmethod

type httpmethod string

const (
  Get  = "Get"
  Post = "Post"
)

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

現(xiàn)在,枚舉創(chuàng)建必須通過方法,我們就可以在其中實(shí)現(xiàn)限定創(chuàng)建規(guī)則。

方法可能挺好,但好像沒見到這么玩的?

為什么呢?

我的猜想是,開發(fā)時(shí)我們不會(huì)隨意創(chuàng)建新的枚舉值,對于邊界數(shù)據(jù)的傳遞,確保通過轉(zhuǎn)化函數(shù)處理不就行了嗎?

真實(shí)場景

對真實(shí)場景下枚舉的使用,有價(jià)值之處主要在與其他系統(tǒng)的對接。

舉例而言,如來自前端 API 或數(shù)據(jù)庫,有時(shí)可能出現(xiàn)一些異常值。對這類場景,通過前面介紹,可提供轉(zhuǎn)化函數(shù),在其中設(shè)置檢查規(guī)則。如果發(fā)現(xiàn)異常選擇丟棄,執(zhí)行如 error 或 panic。

而對于內(nèi)部系統(tǒng),如果使用類似于 protobuffer 協(xié)議,可在協(xié)議上限定好枚舉范圍,標(biāo)記異常數(shù)據(jù)等。

當(dāng)然,可能出現(xiàn)因?yàn)榘l(fā)布時(shí)間次序或者兄弟團(tuán)隊(duì)忘記通知等,導(dǎo)致系統(tǒng)間枚舉值對不齊的情況,也會(huì)按上面的邏輯丟棄、error 等,便于即使發(fā)現(xiàn)。

對于團(tuán)隊(duì)合作這類場景,最好的解決方式,還是要在設(shè)計(jì)系統(tǒng)時(shí),考慮上下游的兼容性,而不是每當(dāng)有變動(dòng),全員亂糟糟,這最容易導(dǎo)致生產(chǎn)事故了。

其實(shí)無論哪一種情況,重點(diǎn)在于保證進(jìn)入系統(tǒng)的數(shù)據(jù)是否可通過轉(zhuǎn)化檢測,而不是多此一舉,限制類似于 HttpMethod("Get") 的類型轉(zhuǎn)化,因?yàn)闆]有人會(huì)這么寫代碼。

總結(jié)

Go 語言中,枚舉的表達(dá)方式多種多樣。從簡單的 iota 到復(fù)雜的結(jié)構(gòu)體方式,每種方法都有其適用場景。作為開發(fā)者,最好是根據(jù)自己的具體需求,選擇合適的實(shí)現(xiàn)方式。

到此這篇關(guān)于Go語言中實(shí)現(xiàn)enum枚舉的方法詳解的文章就介紹到這了,更多相關(guān)Go enum枚舉內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go語言規(guī)范RESTful?API業(yè)務(wù)錯(cuò)誤處理

    go語言規(guī)范RESTful?API業(yè)務(wù)錯(cuò)誤處理

    這篇文章主要為大家介紹了go語言規(guī)范RESTful?API業(yè)務(wù)錯(cuò)誤處理方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解

    go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解

    這篇文章主要為大家介紹了go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • GO實(shí)現(xiàn)協(xié)程池管理的方法

    GO實(shí)現(xiàn)協(xié)程池管理的方法

    這篇文章給大家介紹GO實(shí)現(xiàn)協(xié)程池管理的方法,分別使用channel實(shí)現(xiàn)協(xié)程池和消費(fèi)者模式實(shí)現(xiàn)協(xié)程池,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-07-07
  • GoAdminGroup/go-admin的安裝和運(yùn)行的教程詳解

    GoAdminGroup/go-admin的安裝和運(yùn)行的教程詳解

    這篇文章主要介紹了GoAdminGroup/go-admin的安裝和運(yùn)行的教程詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能示例

    golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能示例

    這篇文章主要介紹了golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • golang logrus日志框架實(shí)例詳解

    golang logrus日志框架實(shí)例詳解

    logrus是一個(gè)可插拔的、結(jié)構(gòu)化的日志框架,這篇文章主要介紹了golang logrus日志框架實(shí)例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • 一文帶你了解Go語言中的匿名函數(shù)

    一文帶你了解Go語言中的匿名函數(shù)

    無論是在Go語言還是其他編程語言中,匿名函數(shù)都扮演著重要的角色,本文將詳細(xì)介紹Go語言中匿名函數(shù)的概念和使用方法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • Go語言基礎(chǔ)go doc命令用法及示例詳解

    Go語言基礎(chǔ)go doc命令用法及示例詳解

    這篇文章主要為大家介紹了Go語言基礎(chǔ)go doc命令的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步
    2021-11-11
  • golang如何讓string轉(zhuǎn)int64

    golang如何讓string轉(zhuǎn)int64

    這篇文章主要介紹了golang如何讓string轉(zhuǎn)int64問題,
    2024-02-02
  • 淺談golang并發(fā)操作變量安全的問題

    淺談golang并發(fā)操作變量安全的問題

    這篇文章主要介紹了淺談golang并發(fā)操作變量安全的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12

最新評論