Go 学习笔记(七)- 接口

接口类型是对其它类型行为的抽象和概括,我的理解是,可以更方便的处理不同类型数据。在 js 里没有特别的明确数据类型,比如 1 + '2''12'1 - '2'-1,在 go 里这样的语句是错误的。

接口的概念非常多,小章节是之前每个章节的2,3倍。

接口约定

由于 go 是强类型的静态语言,例如一个函数定义时声明了类型,则只能处理该类数据。
如果仅仅是数据类型不同,处理方式一模一样的情况,难道要重写一份吗?肯定不是这样的。

接口类型,将改变这一切。

最简单的例子,如 fmt.Printf 可以接受任何类型的数据。

1
2
3
4
5
6
7
8
9
10
11
package fmt

func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
func Printf(format string, args ...interface{}) (int, error) {
return Fprintf(os.Stdout, format, args...)
}
func Sprintf(format string, args ...interface{}) string {
var buf bytes.Buffer
Fprintf(&buf, format, args...)
return buf.String()
}

虽然一开始我没看懂,但是看完后才发现原来如此简单。
参数部分是 args ...interface{},是一个空接口,可以理解为接收任意类型的参数。

其中 io.Writer 是‘可写’接口:

1
2
3
4
5
package io

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

这么看感觉只是把函数声明写到这里而已,确实是这样的,把函数声明写一遍即可。
这个接口的意思为:某个类型只要有 Write 方法,就可以接收。

1
2
3
4
5
6
type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
*c += ByteCounter(len(p)) // convert int to ByteCounter
return len(p), nil
}

比如这个 int 的别名 ByteCounter 类型,定义了一个 Write 方法。

1
2
3
4
5
var c ByteCounter
var name = "world"
fmt.Fprintf(&c, "hello, %s", name)

fmt.Println(c) // 12

他就可以当做第一个参数被调用,而且正确执行了 Write 方法。

第一小节花的时间略多,因为概念基本在这了,后面只是延伸和应用。

接口类型

接口类型其实跟结构体类似,只是关键词不一样,因为接口也可以像结构体一样声明跟嵌入,这里就不多解释了。

实现接口的条件

一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。
简单说,如果一个接口有 a,b 方法,你的类型必须也有 a,b 方法才能满足这个接口类型,否则无法被赋值或当参数接收。
但如果你的类型有 a,b,c,d 甚至更多方法,那么你的类型也满足了这个接口。
简单说就是只能多不能少,只有被满足的才能被赋值或当参数接收,当然空接口除外,任何数据都满足空接口。

flag.Value 接口

粗略看了,貌似处理命令行输入的,通过 flag.Value 接口,自定义处理控制台输入。

1
2
3
4
type Value interface {
String() string
Set(string) error
}

接口值

概念上讲一个接口的值,接口值,由两个部分组成,一个具体的类型和那个类型的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var w io.Writer
// 它的类型和类型的值,描述如下:
w
┌─────────┐
typenil
├─────────┤
value │ nil
└─────────┘

w = os.Stdout
// 赋值后如下:
w
┌──────────────┐
type │ *os.File │ os.File
├──────────────┤ ┌───────────────────┐
value │ ●───────┼──────➞│ fd int = 1(stdot) │
└──────────────┘ └───────────────────┘

这货说难也难,说简单也简单。

sort.Interface 接口

排序接口,可以对任意数据类型进行排序,只要简单实现3个接口即可。

1
2
3
4
5
6
7
package sort

type Interface interface {
Len() int
Less(i, j int) bool // i, j 是序列中的元素索引(Less类似js数组的sort回调函数,只是这里是索引)
Swap(i, j int)
}

例子就略了。。

http.Handler 接口

略,等学 web 的时候细学。

error 接口

简单粗暴:

1
2
3
type error interface {
Error() string
}

创建一个error最简单的方法就是调用errors.New函数,它会根据传入的错误信息返回一个新的error。
整个errors包仅只有4行:

1
2
3
4
5
6
7
package errors

func New(text string) error { return &errorString{text} }

type errorString struct { text string }

func (e *errorString) Error() string { return e.text }

两个 error 是不相等的。

1
fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false"

类型断言

使用 x.(T) 语法对 x 进行 T 类型的断言。

1
2
3
4
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer

当类型断言的操作对象是一个变量,你有时会看见原来的变量名重用而不是声明一个新的本地变量,这个重用的变量会覆盖原来的值,如下面这样:

1
2
3
if w, ok := w.(*os.File); ok {
// ...use w...
}

小结

后续几个小章结都是应用,等实际应用时踩坑了,光这样死记硬背也没用。