Skip to content

接口

go 的接口是一种类型,他本身不干活,但可以束缚类型的方法,要求不同类型实现一套在调用方看来完全一样的方法,可以称它们是鸭子类型。

  • “鸭子类型”(Duck Typing)“如果它走起来像鸭子,叫起来也像鸭子,那它就是鸭子。”

这种设计叫多态,虽然底层类型、方法内部的逻辑都不同,但其方法的名称、参数和返回值类型都是统一的。

这样可提升代码的可维护性、复用性:接口由使用者定义,实现由提供者完成。

go
package main

import "fmt"

// 1. 定义接口
type Speaker interface {
    Speak() string
}

// 2. 定义结构体(自定义类型)
type Person struct {
    Name string
}

type Cat struct {
    Name string
}

// 3. 给Person实现Speak方法(完全匹配接口的方法签名)
func (p Person) Speak() string {
    return "你好,我是" + p.Name
}

// 4. 给Cat实现Speak方法
func (c Cat) Speak() string {
    return "喵~ 我是" + c.Name
}

func main() {
    // 接口类型变量,可以接收所有实现了该接口的类型实例
    var s Speaker

    s = Person{Name: "张三"}
    fmt.Println(s.Speak()) // 输出:你好,我是张三

    s = Cat{Name: "橘猫"}
    fmt.Println(s.Speak()) // 输出:喵~ 我是橘猫
}

上面的代码里,PersonCat都实现了Speaker接口,这就是 Go 的多态:同一接口类型,不同的底层类型,调用同一个方法,执行不同的逻辑。

偷懒/灵活的隐式实现

哪怕函数/方法没有明确声明实现了接口,只要名称、参数、返回值都符合,那就没问题(一个类型只要实现了接口中定义的所有方法,就隐式地实现了该接口)。

好处:

  • 虽然只能为每个方法节省显式声明的那一句代码量,但你就说有没有减少吧。

接口值

接口是一个类型,可以被变量初始化,接口值是一个元组,里面的数据是这样存放的:(value, type),前者是存放数据的底层类型的具体值,后者为该值的接口类型(非原本的接口类型)。

go
package main

import (
	"fmt"
	"math"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

type F float64

func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

上面的代码中,其实 describe(i I) 才是主角,它规定传入的参数必须实现 I 接口,也就是参数要有个 M() 方法。它不管参数本身是啥类型,它只要参数具备 M() 方法。

实现接口的几个法子总结

  • 直接实现接口要求的所有方法,接口的灵活性大多来源于此。
  • 通过给接口值赋值(隐式实现,赋值时会触发编译器的接口类型检查)。

以我的简单粗俗的性子,写自己的项目时,大概只会用第一种吧。

类型断言

为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。

go
t, ok := i.(T)

i 保存了一个 T,那么 t 将会是其底层值,而 oktrue

否则,ok 将为 falset 将为 T 类型的零值,程序并不会产生 panic。

请注意这种语法和读取一个映射时的相同之处。

go
func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)	// hello

	s, ok := i.(string)
	fmt.Println(s, ok)	// hello true

	f, ok := i.(float64)
	fmt.Println(f, ok)	// 0 false  有 ok 就不会 panic

	f = i.(float64) // panic: interface conversion: interface {} is string, not float64
	fmt.Println(f)
}