Interface 是 Go 语言中及其重要的概念,可以从作为接口约定作为类型值两个方面去理解它。

作为接口约定

接口,可以理解是一种约定俗成,便于各系统间的沟通、协作。

比如不同品牌的显示器要与不同品牌的显卡进行数据交互,若大家都按照自己的方式来,就会乱套。因此定义好 接口 规范,即两个部件间交互的方式,这样不同系统间才能相互协作。比如显示器与显卡间就有 HDMI、VGA 等接口协议。

同样的,在 Go 中使用 interface 关键字,定义好接口规范,方便不同类型的对象间的交互。

例如,Go 标准库中的 fmt 库,定义了 Stringerinterface,这个接口定义了一个方法 String() string

1
2
3
4
5
package fmt

type Stringer interface {
    String() string
}

任何结构体对象只要实现了 String() string 的方法,就可以说该结构体实现了 Stringer,即可使用 fmt.Print 系列方法,自定义该结构体对象的输出结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type MyStruct1 struct {
    Name string
    Age int
}

func (m *MyStruct1) Stringer() string {
	return fmt.Sprintf("Name:%s, Age:%d", m.Name, m.Age)
}

type MyStruct2 struct {
    CurrentHour int
}

func (m *MyStruct2) Stringer() string {
	return fmt.Sprintf("Current Time is %d:00", m.CurrentHour)
}

func main() {
    var s Stringer
    s = MyStruct1{
        Name: "aaa",
        Age: 5
    }
    fmt.Println(s)

    s = MyStruct2{
        CurrentHour: 18
    }
    fmt.Println(s)
}

输出为

1
2
Name:aaa, Age:5
Current Time is 18:00

可看到, Stringer 类型的变量 s,可承载 MyStruct1MyStruct2 两种结构体对象的值,因为他们都实现了 Stringer 这个 interface 中的所有方法 。

fmt.Println() 方法内部,会检查传入的对象是否实现 Stringer,若实现了,则调用对象自己的 String() string 方法,从而达到了自定义 Println() 输出格式的功能。

作为类型值

既然对象须实现 interface 中声明的所有方法,才算是实现了该 interface。那么空的 interface 是不是就能被所有结构体对象所实现?

事实上确实如此。interface{} 类型的变量可接受任何类型的值。

1
2
3
4
5
6
7
8
9
func main() {
    var i interface{}

    i = 5
    i = "aaa"
    i = 6.0

    fmt.Println(i)
}

在以上代码中,即使 i 被不同类型的数据赋值,但不会报错。

interface{} 经常作为函数参数使用,使用时常常搭配 switch类型断言 来判断 interface{} 变量的具体类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func PrintType(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("%d is int type", v)
    case *int:
        fmt.Println("%d is *int type", *v)
    case string:
        fmt.Println("%s is string type", v)
    default:
        fmt.Println("other type")
    }
}

nil 值

其实,interface作为接口约定作为类型值,其实可归为一类,他们的区别是是否有定义方法。对 nil 的判断是不区分这两种情况的。

对于 interface 对象,只有当其类型和值均为 nil 时,它才为 nilnil 的判断与函数形参类型无关,只与对象本身有关。例如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func f1(out io.Writer) {
	if out != nil {
		fmt.Println("a")
	}
}

func main() {
	var buf *bytes.Buffer
	f1(buf)
	f1(nil)
}

var buf *bytes.Buffer 定义了 类型为 *bytes.Buffer 值为 nil 的对象,因此 buf != nil 的值为 truef1(buf) 函数会打印出 a

nil 对象的类型和值均为 nil ,因此 f1(nil) 不会打印出 a

所以最终的输出为

1
a

再来一个类似的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func f2(out interface{}) {
	if out != nil {
		fmt.Println("a")
	}
}

func main() {
	var buf *bytes.Buffer
	f2(buf)
	f2(nil)
}

输出为

1
a

分析原因同上面的例子,就不赘述了。

其他

Go 在 1.17 中,引入了泛型。这里不细说,仅举个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Number interface{
	type int, int8, int16, int32, int64, 
		uint, uint8, uint16, uint32, uint64,
		float32, float64
}

func max[T Number](a, b T) T {
	if a > b {
		return a
	}
	return b
}

func main() {
	fmt.Println(max(1, 2))
}

而在 Go 1.18 中,又有与 interface 相关的改动。即用 any 关键字替代 interface{} ,这样写起来更简洁明了。

1
2
func Print[T interface{}](t T) // before
func Print[T any](t T) // after

例子

接下来两个例子,演示下 interface 的用法。

标准库 io.Writer

首先,标准库中定义了 Writerinterface

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

我们的样例代码如下

1
2
3
4
5
6
7
func main() {
	var buf bytes.Buffer

	fmt.Fprint(&buf, "a", "b", 1)

	fmt.Println(buf.String())
}

输出为 ab1

bytes.Buffer 结构体,实现了 io.Writer 这个 interface,且 fmt.Fprint 函数内部调用了 .Write 方法,因此通过 fmt.Fprint 方法就能直接把字符串写入到另一个库的对象 bytes.Buffer 中。

生成条件嵌套的语句

生成嵌套类型的条件。类似于 SQL 的 where 条件,比如 a=1 and (b=2 or c=3)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
type Stringer interface {
	String() string
}

type Equation struct {
	A string
	B string
}

func (e Equation) String() string {
	return fmt.Sprintf("%s=%s", e.A, e.B)
}

type Condition struct {
	Operation     string
	SubConditions []Stringer
}

func (cond Condition) String() string {
	var subConds []string
	for _, v := range cond.SubConditions {
		subConds = append(subConds, v.String())
	}

	return fmt.Sprintf("(%s)", strings.Join(subConds, " "+cond.Operation+" "))
}

func main() {

	a1 := Equation{A: "a", B: "b"}
	a2 := Equation{A: "c", B: "d"}

	a3 := Condition{
		Operation:     "and",
		SubConditions: []Stringer{a1, a2},
	}

	a4 := Condition{
		Operation:     "or",
		SubConditions: []Stringer{a1, a3},
	}

	fmt.Println(a4.String())
}

输出为 (a=b or (a=b and c=d))

参考

go中的接口与多态

Go语言多态的实现与interface使用

在 Go 语言中,有时 nil 并不是一个 nil

Go语言泛型的进化