编程日记,分享开源库enumer

262 阅读5分钟

背景

hello, 朋友们,相信你已经好久没有看到我发新文章了,好久没有发过动态.其实我有很多东西想要分享,很多话想要说。不过因为一些事情分身无暇,只能把分享欲压住了,后面再把之前想写的内容补补。好了回归正传,最近发现了一个很不错的golang开源库,这个工具可以给我们自己生成枚举值相关的一些常用方法,结合我的开发经验来说,我觉得这个库很有用。我会从如何在go中定义枚举,以及protcol buffer如何定义枚举值相关的方法,最后讲解这个库的用法。给大家分享的同时也是我自己在做相应的总结。

如何定义枚举值?

和其他语言不一样,golang并不直接支持枚举值,一般来说枚举的写法是这样的:

type Language intconst (
  English Language = iota
  Chinese
  Arabic
  French
)

比如我上面这段代码中定义的枚举值,首先我们可以定义一个整数类型,然后用 iota 这个关键字配合刚定义的类型定义你的枚举值,比如上面这段代码的含义就是定义了一个新的叫做语言的枚举值,然后依次排开,English的值是0, Chinese是1,Arabic是2, French是3.

这样写之后我们就定义了每个枚举值,但是这样就够了吗?我感觉远远不够。为什么呢?因为很多时候你是需要写一些业务逻辑的,比如现在我们现在一段逻辑是这样的 "世界末日了,现在有一个方舟,只有会讲中文的人才能上去。" 你的逻辑是要判断这个人会不会讲中文,但是如果来的人是讲英文的,阿拉伯语的,你就需要拦住他,这个时候你需要打印日志。现在的问题,打印的日志要是字符串,但是我们的定义的每个枚举值都是数字,怎么转化为字符串呢?总不能打印个数字到日志里吧,这样看日志太难受了。铁牛(我不吃牛肉那个的那个铁牛)灵机一动, 给这个枚举类型定义一个方法获取相应的字符串不就好了?代码如下:

func (l Language) String() string {
  return []string{"English", "Chinese", "Arabic", "French"}[l]
}

这样就是根据枚举值的值获取相应的字符串返回,测试代码:

func TestLanguageString(t *testing.T) {
  fmt.Println(English.String())
}

这样子就大功告成啦。基本上每次定义枚举值都要给他一个String方法,就我的开发经验而言,基本上String方法是必须的,很多时候都需要这个方法。有没有更好的方法呢?总不能每次自己写一个枚举类型都写一些方法出来吧。还是得想想办法如何可以简化和优化这个过程。下面我们可以看看protocol buffer是怎么做的。

protocol buffer是怎么做的

syntax = "proto3";
package enum_test;
option go_package = "proto/elliot_enum";
​
enum Book {
 Unknown = 0;WAR_AND_PEACE = 1;PRIDE_AND_PREJUDICE = 2;
}

首先我们定义这样一个protoc,里面定义了Book这个枚举类型,以及相应的两本书,一是《世界与和平》而是《傲慢与偏见》 然后运行命令把相应的go代码生成出来。

type Book int32const (
  Book_Unknown             Book = 0
  Book_WAR_AND_PEACE       Book = 1
  Book_PRIDE_AND_PREJUDICE Book = 2
)
​
// Enum value maps for Book.
var (
  Book_name = map[int32]string{
    0: "Unknown",
    1: "WAR_AND_PEACE",
    2: "PRIDE_AND_PREJUDICE",
  }
  Book_value = map[string]int32{
    "Unknown":             0,
    "WAR_AND_PEACE":       1,
    "PRIDE_AND_PREJUDICE": 2,
  }
)
​
func (x Book) Enum() *Book {
  p := new(Book)
  *p = x
  return p
}
​
func (x Book) String() string {
  return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
​
func (Book) Descriptor() protoreflect.EnumDescriptor {
  return file_enum_test_proto_enumTypes[0].Descriptor()
}
​
func (Book) Type() protoreflect.EnumType {
  return &file_enum_test_proto_enumTypes[0]
}
​
func (x Book) Number() protoreflect.EnumNumber {
  return protoreflect.EnumNumber(x)
}

可以看出,生成出来的代码给出了一些通用的方法,包括枚举转字符串,字符串转数字等等。其实一开始的时候我就关注到protocol buffer这个做法,只是总不能每次想写个枚举都这样子搞吧,也不太现实。

开源库enumer

这个开源库是我有一天偶然看到,和我之前的一些想法是一致的。就是如果我按照一开始的方定义了一些枚举值之后,是不是有一个比较通用的方式给这个枚举值生成相应的方法,这样可以省去一些的人力成本,以及增加代码的可维护性呢?这个库github.com/dmarkham/en…正好为我们做到了这一点。怎么做呢?且看我娓娓道来:

//go:generate go run github.com/dmarkham/enumer -type=DayOfWeek
type DayOfWeek intconst (
  Sunday DayOfWeek = iota
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)

这个是我定义的枚举值,定义了一周之中的每一天。在相应的类型上面的这一行代码就可以帮我们生成相对应的一些比较通用的方法。生成的代码比较多,我就不都贴出来了。下面给出一些方法的用法。

func TestEnum(t *testing.T) {
  // 给出相应的字符串
  fmt.Println(Thursday.String())
  // 判断是不是枚举值中的一员
  fmt.Println(Sunday.IsADayOfWeek())
  // DayOfWeekString给出相应星期几的字符串。感觉这个方法比较鸡肋。。
  fmt.Println(DayOfWeekString("sunday"))
}

总结

总的来说可以尝试尝试这个库,可以帮助你给枚举值提供相应的通用方法,如果团队内部都适用的话,团队内部可以养成相应的共识,从而提升生产力。

参考资料

  1. 开源库地址:github.com/dmarkham/en…