首頁 > 軟體

Go專案怎麼使用列舉

2022-08-22 22:00:18

前言

哈嘍,大家好,我是asong。列舉是一種很重要的資料型別,在java、C語言等主流程式語言中都支援了列舉型別,但是在Go語言中卻沒有列舉型別,那有什麼替代方案嗎? 本文我們來聊一聊這個事情;

為什麼要有列舉

我們以java語言為例子,在JDK1.5之前沒有列舉型別,我們通常會使用int常數來表示列舉,一般使用如下:

public static final int COLOR_RED = 1;
public static final int COLOR_BLUE = 2;
public static final int COLOR_GREEN = 3;

使用int型別會存在以下隱患:

  • 不具備安全性,宣告時如果沒有使用final就會造成值被篡改的風險;
  • 語意不夠明確,列印int型數位並不知道其具體含義

於是乎我們就想到用常數字元來表示,程式碼就變成了這樣:

public static final String COLOR_RED = "RED";
public static final String COLOR_BLUE = "BLUE";
public static final String COLOR_GREEN = "GREEN";

這樣也同樣存在問題,因為我們使用的常數字元,那麼有些程式猿不按套路出牌就可以使用字串的值進行比較,這樣的程式碼會被不斷模仿變得越來越多的,然後屎山就出現了;

所以我們迫切需要列舉型別的出現來起到約束的作用,假設使用一個列舉型別做入參,列舉型別就可以限定沙雕使用者不按套路傳參,這樣就可以懟他了,哈哈~;

使用列舉的程式碼就可以變成這樣,傳了列舉之外的型別都不可以了;

public class EnumClass {
    public static void main(String [] args){
        Color color = Color.RED;
        convert(color);
        System.out.println(color.name());

    }

    public static void convert(Color c){
        System.out.println(c.name());
    }

}

enum Color{
    RED,BLUE,GREEN;
}

Go語言就沒有列舉型別,我們該使用什麼方法來替代呢?

定義新型別實現列舉

列舉通常是一組相關的常數集合,Go語言中有提供常數型別,所以我們可以使用常數來宣告列舉,但也同樣會遇到上述的問題,起不到約束的作用,所以為了起到約束我們可以使用Go語言另外一個知識點 -- 型別定義,Go語言中可以使用type關鍵字定義不同的型別,我們可以為整型、浮點型、字元型等定義新的型別,新的型別與原型別轉換需要顯式轉換,這樣在一定程度上也起到了約束的作用,我們就可以用Go語言實現如下列舉:

type OrderStatus int

const (
    CREATE OrderStatus = iota + 1
    PAID
    DELIVERING
    COMPLETED
    CANCELLED
)

func main() {
    a := 100
    IsCreated(a)
}

上面的程式碼就會報錯:

./main.go:19:12: cannot use a (variable of type int) as type OrderStatus in argument to IsCreated

定義新的型別可以起到約束作用,比如我們要檢查狀態機,入參限定了必須是OrderStatus型別,如果是int型別就會報錯。

上面我們的列舉實現方式只能獲取列舉值,獲取不到其對映的字面意思,所以我們可以優化一下,實現String方法,使用官方提供的cmd/string來快速實現,程式碼如下:

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

const (
    CREATE OrderStatus = iota + 1
    PAID
    DELIVERING
    COMPLETED
    CANCELLED
)

執行命令go generate ./...生成orderstatus_string.go檔案:

import "strconv"

func _() {
    // An "invalid array index" compiler error signifies that the constant values have changed.
    // Re-run the stringer command to generate them again.
    var x [1]struct{}
    _ = x[CREATE-1]
    _ = x[PAID-2]
    _ = x[DELIVERING-3]
    _ = x[COMPLETED-4]
    _ = x[CANCELLED-5]
}

const _OrderStatus_name = "CREATEPAIDDELIVERINGCOMPLETEDCANCELLED"

var _OrderStatus_index = [...]uint8{0, 6, 10, 20, 29, 38}

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

protobuf中生成的列舉程式碼

Go語言使用protobuf會生成對應的列舉程式碼,我們發現其中也是使用定義新的型別的方式來實現的,然後在封裝一些方法,我們來賞析一下protobuf生成的列舉程式碼:

const (
    CREATED  OrderStatus = 1
    PAID OrderStatus = 2
    CANCELED OrderStatus = 3
)

var OrderStatus_name = map[int32]string{
    1: "CREATED",
    2: "PAID",
    3: "CANCELED",
}

var OrderStatus_value = map[string]int32{
    "CREATED":  1,
    "PAID": 2,
    "CANCELED": 3,
}

func (x OrderStatus) Enum() *OrderStatus {
    p := new(OrderStatus)
    *p = x
    return p
}

func (x OrderStatus) String() string {
    return proto.EnumName(OrderStatus_name, int32(x))
}

func (x *OrderStatus) UnmarshalJSON(data []byte) error {
    value, err := proto.UnmarshalJSONEnum(OrderStatus_value, data, "OrderStatus")
    if err != nil {
        return err
    }
    *x = OrderStatus(value)
    return nil
}

總結

雖然Go語言沒有提供列舉型別,但是我們也可以根據Go語言的兩個特性:常數和定義新型別來實現列舉,方法總比困難多嗎,開源庫是優秀的,我們往往可以從高手那裡裡學習很多,記住,請永遠保持一個學徒之心;

到此這篇關於Go專案怎麼使用列舉的文章就介紹到這了,更多相關Go 列舉內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com