使用Golang stdlib接口

119 阅读5分钟

在这篇文章中,我将向你展示如何使用Go语言的两个最令人兴奋的功能:它的标准库(标题中的stdlib)和它的接口。

由于其强大的标准库,Go以提供大量的功能而闻名。涵盖了从文本和JSON转换到数据库和HTTP服务器的所有内容,我们可以开发复杂的应用程序,而无需导入第三方包。

该语言的另一个基本特征是其接口的力量。与面向对象的语言不同,Go没有extends 关键字,允许我们使用变量、结构体、片断等实现一个接口。只要实现接口中定义的相同的函数签名,就可以了。

让我们利用这两个特点来改进我们的应用代码。

实现错误接口#

我们要探讨的第一个接口是error

type error interface {
	Error() string
}

我们可以使用任何实现这个接口的结构或变量作为函数和测试中的错误。

package main

import (
	"fmt"
)

type MyError struct {
	Message string
}

func (m MyError) Error() string {
	return fmt.Sprintf("Message: %s", m.Message)
}

func main() {
	_, err := divide(10, 0)
	if err != nil {
		fmt.Println(err)
	}
}

func divide(x, y int) (float64, error) {
	if y <= 0 {
		return 0.0, MyError{
			Message: "error in divide function",
		}
	}
	return float64(x / y), nil
}

我在本例中创建了结构MyError ,并按照接口的定义实现了Error 函数。现在该结构可以作为错误返回到divide 函数中。由于这个功能,我们可以为我们的应用程序创建具有额外信息、日志和其他功能的自定义错误。

实现 fmt.Stringer 和 fmt.Former。Stringer 和 fmt.Formatter 接口#

对于下一个例子,我创建了一个叫做Level, 的类型,它是一个int. 我们可以在一个生成应用程序日志的库中使用这个类型,而且它是一个整数的事实允许我们做像if os.Getenv('ENV') == "prod" && level < INFO 的逻辑来控制哪些消息应该被处理或不被处理。

但是,尽管使用这种类型的逻辑很方便,就像上面描述的那样,在某些情况下,将这个值转换为string ,会有帮助。所以这就是我们要做的,通过实现fmt.Stringerfmt.Formatter 接口。

type Stringer interface {
	String() string
}
type Formatter interface {
	Format(f State, c rune)
}

我们的示例代码是。

package main

import (
	"fmt"
	"strings"
)

type Level int

const (
	DEBUG Level = iota + 1
	INFO
	NOTICE
	ALERT
	WARN
	ERROR
	CRITICAL
	FATAL
	DISASTER
)

var toString = map[Level]string{
	DEBUG:    "DEBUG",
	INFO:     "INFO",
	NOTICE:   "NOTICE",
	ALERT:    "ALERT",
	WARN:     "WARN",
	ERROR:    "ERROR",
	CRITICAL: "CRITICAL",
	FATAL:    "FATAL",
	DISASTER: "DISASTER",
}

func (l Level) String() string {
	return toString[l]
}

func (l Level) Format(f fmt.State, c rune) {
	switch c {
	case 'l':
		fmt.Fprint(f, strings.ToLower(toString[l]))
	default:
		fmt.Fprintf(f, toString[l])
	}
}
func main() {
	l := DEBUG
	fmt.Println(l)
	fmt.Printf("Level: %l\n", l)
}

String() 函数被fmt.Println(l) 函数使用,也被fmt.Printf 函数使用。在这个例子中,我实现了Format 函数来演示我们如何创建特殊的格式化,在这个例子中,%l, ,我定义为负责将值转化为小写字母。

实现json.Marshaler接口#

现在让我们创建一个新的结构,Log, ,其中包含一个Level

type Log struct {
	Message string `json:"message"`
	Level   Level  `json:"level"`
}

日志包中的一个常见功能是将数据转换为JSON。

log := Log{
		Message: "Message log",
		Level:   ERROR,
}
j, _ := json.Marshal(log)
fmt.Println(string(j))

但结果并不完全如预期,因为代码中生成的Level 是一个整数。

{"message":"Message log","level":6}

为了快速解决这个问题,我们可以实现json.Marshaler 接口。

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

实现的结果是这样的。

func (l Level) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString(`"`)
	buffer.WriteString(toString[l])
	buffer.WriteString(`"`)
	return buffer.Bytes(), nil
}

而现在的打印结果和我们预期的一样。

{"message":"Message log","level":"ERROR"}

实现sort.Interface接口#

structs, 在下面的例子中,我们将对slice ,这种逻辑出现在很多场景中。但是,首先,让我们创建我们要排序的数据。

package main

import (
	"fmt"
)

type Movie struct {
	ReleaseYear int
	Title       string
}

func main() {
	movies := []*Movie{
		&Movie{
			ReleaseYear: 2022,
			Title:       "The Northman",
		},
		&Movie{
			ReleaseYear: 1994,
			Title:       "Pulp Fiction",
		},
		&Movie{
			ReleaseYear: 1999,
			Title:       "Matrix",
		},
	}
	for _, m := range movies {
		fmt.Println(m)
	}
}

现在让我们对我们的片断进行排序,首先按照发布的顺序。为此,我们需要实现sort.Interface 接口。

type Interface interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

为此,我添加了以下代码片段。

type byReleaseDate []*Movie

func (e byReleaseDate) Len() int           { return len(e) }
func (e byReleaseDate) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
func (e byReleaseDate) Less(i, j int) bool { return e[i].ReleaseYear < e[j].ReleaseYear }

并在main 函数中,在打印电影的循环之前。

sort.Sort(byReleaseDate(movies))

我们可以在其他情况下也这样做。下面的代码是完整的例子,有一个以上的排序,并实现了fmt.Stringer 接口以方便打印电影。

package main

import (
	"fmt"
	"sort"
)

type Movie struct {
	ReleaseYear int
	Title       string
}

type byReleaseDate []*Movie

func (e byReleaseDate) Len() int           { return len(e) }
func (e byReleaseDate) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
func (e byReleaseDate) Less(i, j int) bool { return e[i].ReleaseYear < e[j].ReleaseYear }

type byTitle []*Movie

func (e byTitle) Len() int           { return len(e) }
func (e byTitle) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
func (e byTitle) Less(i, j int) bool { return e[i].Title < e[j].Title }

func (m Movie) String() string {
	return fmt.Sprintf("%s was released at %d", m.Title, m.ReleaseYear)
}

func main() {
	movies := []*Movie{
		&Movie{
			ReleaseYear: 2022,
			Title:       "The Northman",
		},
		&Movie{
			ReleaseYear: 1994,
			Title:       "Pulp Fiction",
		},
		&Movie{
			ReleaseYear: 1999,
			Title:       "Matrix",
		},
	}
	sort.Sort(byReleaseDate(movies))
	for _, m := range movies {
		fmt.Println(m)
	}
	fmt.Println("====")
	sort.Sort(byTitle(movies))
	for _, m := range movies {
		fmt.Println(m)
	}
}

执行的结果是。

Pulp Fiction was released at 1994
Matrix was released at 1999
The Northman was released at 2022
====
Matrix was released at 1999
Pulp Fiction was released at 1994
The Northman was released at 2022

还有更多...#

除了我在这里展示的例子外,最著名的也许是http.Handler 接口的实现,以开发Rest API。该接口。

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

以及最直接的执行。

package main

import (
	"fmt"
	"net/http"
)

type helloHandler struct{}

func (h helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "HeloWorld")
}

func main() {
	http.Handle("/hello", helloHandler{})
	http.ListenAndServe(":8090", nil)
}

但由于这个例子非常有名,我就不深入研究了。

Go的stdlib有很多包和接口,我们可以通过实现和扩展来开发复杂的应用程序。我建议大家研究一下文档,找到更多有趣和有价值的功能。