重学Go语言 | Go六个常用接口的使用

公众号:程序员读书;欢迎关注

在上一篇文章中,我们讲解了接口,对于很多初学者来说,接口很抽象,我们有时候不知道如何定义自己的接口,基于此,在这篇文章中,我们来学习几个Go标准库的接口,看看Go标准库是如何定义接口,以加深对Go语言接口的理解。

fmt.Stringer

在开发过程,我们经常会调用fmt包下的打印函数(如printlnprintf)将调试信息输出到控制台:

go 复制代码
fmt.Println("test")
fmt.Printf("%d\n",10)

这些打印函数会自动决定如何在控制台输出这些信息,对于自定义类型,如果我们想自定义其在控制台的输出,要怎么做呢?

fmt包的Stringer用于定义类型的格式化输出,该接口的定义如下:

go 复制代码
type Stringer interface {
    String() string
}

对于实现了Stringer接口的类型,打印函数会自动调用该类型的String()方法,将该方法的返回值输出到控制台,比如我们自定义一个Reason类型,用于表示季节:

go 复制代码
package main 

type Reason uint

const (
	SPRING Reason = iota + 1
	SUMMER
	AUTUMN
	WINTER
)

func main() {
	fmt.Println(SPRING) //输出:1
    fmt.Println(SUMMER) //输出:2
  	fmt.Println(AUTUMN) //输出:3
  	fmt.Println(WINTER) //输出:4
}

实现Stringer接口后,就可以将Reason类以中文的格式打印出来了:

go 复制代码
func (r Reason) String() string {
	return ReasonText[r] //自定义输出:将数值转化为文本
}

var ReasonText = map[Reason]string{
	SPRING: "春天",
	SUMMER: "夏天",
	AUTUMN: "秋天",
	WINTER: "冬天",
}

func main() {
	fmt.Println(SPRING) //输出:春天
  fmt.Println(SUMMER) //输出:夏天
  fmt.Println(AUTUMN) //输出:秋天
  fmt.Println(WINTER) //输出:冬天
}

sort.Interface

除了格式化输出信息外,排序功能也是开发中经常用到的,Go标准库的sort包的Sort()就是常用的排序函数,该函数定义如下:

go 复制代码
func Sort(data Interface) {
	n := data.Len()
	quickSort(data, 0, n, maxDepth(n))
}

可以看到,Sort函数接收一个Inferface类型的参数,Interface类型是一个接口,其定义如下:

go 复制代码
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

Interface类型的Len方法用于返回长度,Less方法用于元素比较大小,Swap方法实现元素位置交换,任何拥有这个方法的类型,都可以传递给sort.Sort进行排序。

下面是一个实现sort.Interface接口,并调用sort.Sort函数的示例:

go 复制代码
package main

import (
	"fmt"
	"sort"
)

type Student struct {
	ID    int
	Name  string
	Score int
}

type Students []Student

func (s Students) Len() int {
	return len(s)
}
func (s Students) Less(i, j int) bool {
	return s[i].Score > s[j].Score
}
func (s Students) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func main() {
	students := []Student{
    {ID: 1, Name: "A", Score: 95},
		{ID: 2, Name: "B", Score: 100},
		{ID: 3, Name: "C", Score: 90},
    {ID: 4, Name: "D", Score: 80},             
	}
	sort.Sort(Students(students))
	fmt.Println(students)
}

io.Reader和io.Writer

网络数据的读取与发送、文件的读取与写入,本质都是写入或取出一段字节数据(即字节数组),Go标准库对字节的读取与写入抽象为io包的ReaderWriter接口:

go 复制代码
type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

在Go标准库内有很多实现了io.Readerio.Writer接口,比如os.File或者Response.Boy:

go 复制代码
package main

import (
	"io"
	"net/http"
	"os"
)

func main() {
	url := ""
	response, err := http.Get(url)

	if err != nil {
		panic(err)
	}
	//os.Stdout是os.File类型
	io.Copy(os.Stdout, response.Body)
}

上面我们调用io.Copy方法将请求到的数据输出到控制台,io.Copy函数定义如下:

go 复制代码
func Copy(dst Writer, src Reader) (written int64, err error) {
	return copyBuffer(dst, src, nil)
}

可以看到这个方法接收的参数就是WriterReader接口,我们也可以自定义类型来实现Writer或者Reader接口:

go 复制代码
package main

import (
	"fmt"
	"io"
	"net/http"
)

type Data string

func (d *Data) Write(p []byte) (n int, err error) {
	n = len(p)
	*d = Data(string(p))
	return n, nil
}

func main() {
	url := ""
	response, err := http.Get(url)

	if err != nil {
		panic(err)
	}
	var d Data
	io.Copy(&d, response.Body)
	fmt.Println(d)
}

error

Go语言的函数支持多个返回值,一般推荐把error类型作为函数最后一个返回值,用于告诉调用者函数调用是否发生错误,error类型实际上就是一个接口:

go 复制代码
type error interface {
    Error() string
}

可以看到error只定义了一个方法,该方法返回一个字符串的错误信息,我们可以使用errors包的方法创建并返回一个error类型:

go 复制代码
var err error = errors.New("Not Found")

也可以在实现error接口的基础,包含更多的错误信息,方便调用者判断错误类型:

go 复制代码
package main

import (
	"fmt"
	"os"
)

type FileNotFound struct {
	Message  string
	FileName string
	err      error
}

func (f FileNotFound) Error() string {
	return f.Message
}

func GetLogFile(fileName string) (*os.File, error) {
	f, err := os.Open(fileName)
	if err != nil {
		return nil, &FileNotFound{FileName: fileName, err: err, Message: "Not found"}
	}
	return f, nil
}

func main() {
	var err error
	f, err = GetLogFile("1.txt")

	if e, ok := err.(FileNotFound); ok {
		fmt.Println(e.Message)
	}
}

http.Handler

http包的Handler接口定义如下,该接口定义了处理HTTP请求应该实现的方法。

go 复制代码
type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

Go语言中,只需要简单的几行代码便可以启动一个Web服务器:

go 复制代码
package main

import "net/http"

func main() {
	http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("User Info"))
	})
	http.ListenAndServe(":8080", nil)
}

http.HandleFunc()会将我们自己的匿名函数封装为HandlerFunc函数,HandlerFunc函数的定义如下,可以看到这个函数实现了Handler接口:

go 复制代码
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

关于Go的Web开发部分,我们后面在其他文章再详细讲解!

小结

几个Go接口讲解下来,你会发现,其实Go标准库定义的接口简单且通用,一个接口就只描述一种行为,这就是Go语言的编程哲学是一个接口只做一件事,只完成一个功能,将多个功能堆砌在同一个接口内是不可取的。

另外也不要在开发某个具体类型前预定义好接口,而是当多个类型有共同行为但却有不同实现的时候,才用接口加以概括和描述。

相关推荐
Java内核笔记13 分钟前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端
码栈研说26 分钟前
Go 语言大白话入门 10 - 排序与常用数据操作
后端·程序员
JavaGuide30 分钟前
再见 Superpowers!很多 Skill 真的可以扔掉了。
后端·ai编程
Java开发追求者1 小时前
Go环境开发配置
go·go环境开发配置·环境开发配置
小阿鑫2 小时前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
ai·程序员·agent·rd270q·明基rd270q
用户7713970207062 小时前
我在项目里发现了一个“神秘文件“——.editorconfig
后端
盏灯2 小时前
mac 外接磁盘,热更新失效
前端·后端
ihuyigui2 小时前
海外签收通知短信接口
android·java·开发语言·前端·数据库·后端
腾渊信息科技公司3 小时前
Spring Boot + TDengine:工业视觉检测数据实时同步方案实战
spring boot·后端·tdengine
用户40966601317513 小时前
MyBatis、MyBatis-Plus、通用 Mapper:一张图说清三者的血缘关系
后端