「译」在 Golang 中实现枚举类型

栏目: Go · 发布时间: 6年前

内容简介:在这篇文章中,我们将介绍使用 go generate 和 abstract 语法树遍历生成强大的枚举类型。这篇文章描述用于生成的 CLI,

原文地址

在这篇文章中,我们将介绍使用 go generate 和 abstract 语法树遍历生成强大的枚举类型。

这篇文章描述用于生成的 CLI, 完全的原代码 可以在 Github 上找到。

Go 中惯用法

Go 语言实际上没有对枚举类型提供完成的支持。定义枚举类型的其中一种方法就是把一类相关变量定义成一种类型。Iota 可以用于定义连续的递增的整数常量。我们可以像这样定义一个 Color 类型。

https://play.golang.org/p/1Zib29yiuFy

package main

import "fmt"

type Color int

const (
    Red Color = iota // 0
    Blue             // 1
)

func main() {
    var b1 Color = Red
    b1 = Red
    fmt.Println(b1) // prints 0

    var b2 Color = 1
    fmt.Println(b2 == Blue) // prints true

    var b3 Color
    b3 = 42
    fmt.Println(b3)  // prints 42
}

这种写法在 Go 代码中很常见,虽然这种方法很常用,但是有一些缺点。因为任何整数都可以给 Color 赋值,所以无法进行使用静态检查。

  • 缺乏序列化——虽然这个不经常使用(开发者想要序列化这个整数,用于传参或者记录到数据库)
  • 缺乏可读性的值——我们需要将 const 值转化成代码中显示的值

了解一种语言的习惯用法以及何时该打破这种习惯很重要。习惯用法往往会限制我们的 “视野”,这有时候恰恰是缺乏创造力的原因。

设计枚举类型

简洁是 Go 语言最重要的特性之一,其他语言的开发者可以很快上手。从另一方面看,可能会产生约束,比如缺乏泛型机制导致许多重复的代码。为了克服这些缺点,社区已经使用代码生成作为定义更强大和灵活类型的机制。

我们就使用这种方法来定义枚举类型,这种方法是使用生成的枚举作为 struct。我们还可以添加方法到 struct 中,struct 还支持 tag,这对于定义显示值和描述很有用。

type ColorEnum struct {
    Red  string `enum:"RED"`
    Blue string `enum:"BLUE"`
}

现在我们需要做的是给每个字段生成结构的实例。

var Red  = Color{name: "RED"}
var Blue = Color{name: "BLUE"}

添加方法到 Color struct 支持 JSON 编码/解码,我们实现 Marshaler 的 interface 支持 JSON 的编码。

func (c Color) MarshalJSON() ([]byte, error) {
    return json.Marshal(c.name)
}

在这个类型序列化时候将会调用我们的自定义实现。同样我们可以实现 Unmarshaler 的 interface,这将让我们可以在代码中使用类型——这允许我们直接在 API 的数据传输对象上定义枚举。

func (c *Color) UnmarshalJSON(b []byte) error {
    return json.Unmarshal(b, c.name)
}

我们还可以定义一些辅助的方法来生成显示的值。

// ColorNames returns the displays values of all enum instances as a slice
func ColorNames() []string { ... }

我们也希望从字符串生成枚举实例,所以添加还需要添加这个方法。

// NewColor generates a new Color from the given display value (name)
func NewColor(value string) (Color, error) { ... }

这些行为都是可扩展的,你可以添加其他方法来返回名字,通过显示 Error() string 来支持错误,并且通过 String() string 来支持 Stringer。

生成代码

遍历抽象语法树

在渲染模板之前,我们需要在源码中解析出 ColorEnum 类型。两种常用的方法是使用 reflet 包和 ast 包。我们需要扫描包级别的 struct。 ast 包具有生成抽象语法树的能力——一种可表示 Go 源码的可遍历数据结构。我们可以遍历语法树并且匹配提供的类型,然后可以解析类型和定义的 struct tag,并用在构建模型已生成模板。我们先加载一个 go 包。

cfg := &packages.Config{
    Mode:  packages.LoadSyntax,
    Tests: false,
}
pkgs, err := packages.Load(cfg, patterns...)

pkgs 变量中包含每个文件的语法树。使用 ast.Inspect 方法来遍历 AST。它需要为每个遇到的节点调用一个函数,我们以此遍历每个文件并且处理该文件的语法树。

for _, file := range pkg.files {
...
    ast.Inspect(file.file, func(node ast.Node) bool {
        // ...handle node, check if it's something we are interested in
    })
}

使用者应该定义这个函数,然后按照感兴趣的 token 类型进行过滤。你可以通过节点上的此检查来过滤。

node.Tok == token.STRUCT { ... }

在我们的例子中,通过定义了 “enmu:” 标签的 struct 进行过滤。我们只是处理了源码中每个标记,并根据遇到的数据类型进行模型(自定义 Go struct)的构建。

生成源代码

有许多生成代码的方法。 Stringer 工具使用 fmt 包标准输出。虽然这很容易实现,但是随着代码的生成的扩张,这将会变得难以操作和调试。更合理的方式是使用 text/template 包,并且使用 Go 强大的模板库。它允许从模板中分离生成模型的逻辑,从而可以关注点分离和让代码易于推理。生成的类型定义可能如下所示:

// {{.NewType}} is the enum that instances should be created from
type {{.NewType}} struct {
    name  string
}

// Enum instances
{{- range $e := .Fields}}
var {{.Value}} = {{$.NewType}}{name: "{{.Key}}"}
{{- end}}

... code to generate methods

然后我们可以使从模型中渲染出源码

t, err := template.New(tmpl).Parse(tmpl)
if err != nil {
    log.Fatal("instance template parse error: ", err)
}

err = t.Execute(buf, model)

当我们在制作模板时候不要担心格式化的问题。 format 包中有一个方法,它将源码作为参数并且返回格式化的 Go 代码,所以应该应该 Go 帮你处理这个问题。

func Source(src []byte) ([]byte, error) { ... }

总结

在这篇中文,我们研究了一种通过解析 Go 源码来生成枚举类型的方法。此方法可以作为模板来构建所需要的源码和作为其他代码的生成器。我们使用 Go 的 text/template 库可以维护的方式呈现源码。

可以在 Github 阅读 完整的代码


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Unix/Linux编程实践教程

Unix/Linux编程实践教程

Bruce Molay、杨宗源、黄海涛 / 杨宗源、黄海涛 / 清华大学出版社 / 2004-10-1 / 56.00元

操作系统是计算机最重要的系统软件。Unix操作系统历经了几十年,至今仍是主流的操作系统。本书通过解释Unix的工作原理,循序渐进地讲解实现Unix中系统命令的方法,让读者理解并逐步精通Unix系统编程,进而具有编制Unix应用程序的能力。书中采用启发式、举一反三、图示讲解等多种方法讲授,语言生动、结构合理、易于理解。每一章后均附有大量的习题和编程练习,以供参考。 本书适合作为高等院校计算机及......一起来看看 《Unix/Linux编程实践教程》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具