Skip to content
目录

Go 语言笔记:接口

接口

接口是对对象行为的抽象和概括,Go 语言的接口类型是延迟绑定,接口只负责定义对象应该做什么,具体实现由对象本身确定。当一个类型定义了接口中的所有方法,我们认为它实现了该接口。

接口的定义

go
type interfaceName interface {
    method()
}

接口的实现

go
package main

import "fmt"

// 定义 Animal 接口
type Animal interface {
    Eat()
    Play()
}

// 定义 Dog 结构体
type Dog struct {
    name string
}

// 实现 Eat 方法
func (dog Dog) Eat() {
    fmt.Printf("%s is eating bone.\n", dog.name)
}

// 实现 Play 方法
func (dog Dog) Play() {
    fmt.Printf("%s is palying ball.\n", dog.name)
}

// 测试
func main() {
    dog := Dog{name: "Tom"}
    var tom Animal = &dog
    tom.Eat()
    tom.Play()
}

首先我们定义了 Animal 接口,其中有两个方法 EatPlay,接着我们定义了 Dog 结构体且实现了 Animal 的两个方法。当我们注释掉其中一个方法的实现的时候,代码编译会报错。

go
// 我们将Play方法注释掉
//func (dog *Dog) Play() {
//    fmt.Printf("%s is palying ball.", dog.name)
//}

// 该行编译报错
var tom Animal = &dog

编译错误信息如下

go
cannot use '&dog' (type *Dog) as type Animal in assignment
Type does not implement 'Animal' as some methods are missing: Play()

接口的多态

上面的示例,我们定义了结构体 Dog 实现了接口,我们可以再定义一个结构体 Cat 来实现接口,然后定义了 eat 方法,可以传入类型为 Animal 的参数

go
package main

import "fmt"

// 定义 Animal 接口
type Animal interface {
    Eat()
    Play()
}

// 定义 Dog 结构体
type Dog struct {
    name string
}

// 实现 Eat 方法
func (dog Dog) Eat() {
    fmt.Printf("%s is eating bone.\n", dog.name)
}

// 实现 Play 方法
func (dog Dog) Play() {
    fmt.Printf("%s is palying ball.\n", dog.name)
}

// 定义 Cat 结构体
type Cat struct {
    name string
}

// 实现 Eat 方法
func (cat Cat) Eat() {
    fmt.Printf("%s is eating a mouse.\n", cat.name)
}

// 实现 Play 方法
func (cat Cat) Play() {
    fmt.Printf("%s is palying a mouse.\n", cat.name)
}

// 定义函数,入参类型是 **Animal**
func eat(animal Animal) {
    animal.Eat()
}

// 测试
func main() {
    dog := Dog{name: "Tom"}
    cat := Cat{name: "Jerry"}
    eat(dog)
    eat(cat)
}

实现接口的方式

方式一:值类型实现

go
// 定义 Animal 接口
type Animal interface {
    Eat()
    Play()
}

// 定义 Dog 结构体
type Dog struct {
    name string
}

// 值类型实现 Eat 方法
func (dog Dog) Eat() {
    fmt.Printf("%s is eating bone.\n", dog.name)
}

// 值类型实现 Play 方法
func (dog Dog) Play() {
    fmt.Printf("%s is palying ball.\n", dog.name)
}

func main() {
    dog := Dog{name: "Tom"}
    eat(dog)
}

方式二:指针类型实现

go
// 定义 Animal 接口
type Animal interface {
    Eat()
    Play()
}

// 定义 Dog 结构体
type Dog struct {
    name string
}

// 指针类型实现 Eat 方法
func (dog *Dog) Eat() {
    fmt.Printf("%s is eating bone.\n", dog.name)
}

// 指针类型实现 Play 方法
func (dog *Dog) Play() {
    fmt.Printf("%s is palying ball.\n", dog.name)
}

func main() {
    dog := &Dog{name: "Tom"}
    eat(dog)
}

方式三:封装函数

go
func getAnimal() Animal {
    return Cat{name: "Jerry"}
}

func main() {
    cat := getAnimal()
    eat(cat)
}

空接口

空接口即没有定义任何方法的接口,是一种特殊形式的接口,可以说任何类型都至少实现了空接口,空接口表示为 interface{}。

go
func Print(v interface{}) {
    fmt.Printf("%T: %v\n", v, v)
}

func main() {
    Print(1)                  // int: 1
    Print("Hello, Lemon!")    // string: Hello, Lemon!
}

通过上面的示例,可以看出,接口有两个属性,一个类型,一个值,空接口两个参数都是 nil

go
func main() {
    var param interface{}
    fmt.Printf("Type: %T, Value: %v", param, param)  // Type: <nil>, Value: <nil>
}

空接口的妙用

  • 声明一个空接口实例 interface{},该实例能承载任何类型的值
go
func main() {
    var param interface{}
    param = 10
    fmt.Printf("Type: %T, Value: %v", param, param) // Type: int, Value: 10
    param = "Hi, Lemon."
    fmt.Printf("Type: %T, Value: %v", param, param) // Type: string, Value: Hi, Lemon.
}
  • 定义一个接受任何类型的数组、切片、Map、结构体
go
func main() {
    x := make([]interface{}, 3)
    x[0] = "Hi, Lemon."
    x[1] = 10
    x[2] = []int{0, 1, 2}
    for _, value := range x {
        fmt.Println(value)
    }
}

空接口的使用禁忌

  • 空接口对象不能赋值给固定类型的对象
go
func main() {
    var age = 10
    var param interface{} = age
    // 编译错误:cannot use 'param' (type interface{}) as string in assignment
    var str string = param 
}
  • 当空接口承载数组或切片后,该对象无法再进行切片
go
func main() {
    var slice = []int{0, 1, 2}
    var param interface{} = slice
    // 编译错误:cannot slice param (type interface{})
    var newSlice = param[1:2]
    fmt.Println(newSlice)
}

接口类型断言

类型断言用于提取接口的底层值,可以使用 interface.(Type) 获取接口的底层值,其中接口 interface 的具体类型是 Type

go
func printTypeValue(itf interface{}) {
    switch itf.(type) {
    case int:
        fmt.Printf("Type: int, Value: %d\n", itf.(int))
    case string:
        fmt.Printf("Type: string, Value: %s\n", itf.(string))
    case Cat:
        fmt.Printf("Type: Cat, Value: %s\n", itf.(Cat))
    case Dog:
        fmt.Printf("Type: Dog, Value: %s\n", itf.(Dog))
    default:
        fmt.Printf("Unknown type\n")
    }
}

func main() {
    age := 10
    name := "Lemon"
    cat := Cat{name: "Jerry"}
    dog := Dog{name: "Tom"}
    printTypeValue(age)  // Type: int, Value: 10
    printTypeValue(name) // Type: string, Value: Lemon
    printTypeValue(cat)  // Type: Cat, Value: {Jerry}
    printTypeValue(dog)  // Type: Dog, Value: {Tom}
}

多接口实现

go
// 定义 Animal 接口
type Animal interface {
    Eat()
    Play()
}

// 定义 Performance 接口
type Performance interface {
    Show()
}

// 定义 Cat 结构体
type Cat struct {
    name string
}

// 实现 Eat 方法
func (cat Cat) Eat() {
    fmt.Printf("%s is eating a mouse.\n", cat.name)
}

// 实现 Play 方法
func (cat Cat) Play() {
    fmt.Printf("%s is palying a mouse.\n", cat.name)
}

// 实现 Show 方法
func (cat Cat) Show() {
    fmt.Printf("%s is showing.\n", cat.name)
}

此时 Cat 实现了多个接口,那么进行类型断言的时候到底以哪个为准呢?答案是:哪个case语句在前就认定是哪个接口类型

go
func printTypeValue(itf interface{}) {
    switch itf.(type) {
    case Performance:
        fmt.Printf("Type: Performance, Value: %s\n", itf.(Performance))
    case Animal:
        fmt.Printf("Type: Animal, Value: %s\n", itf.(Animal))
    default:
        fmt.Printf("Unknown type\n")
    }
}

func main() {
    cat := Cat{name: "Jerry"}
    printTypeValue(cat)  // Type: Performance, Value: {Jerry}
}

接口嵌套

一个接口中可以包含其他的接口,称之为接口嵌套。我们可以在 io 包中看到: ReadWriter 接口中嵌套了 ReaderWriter 两个接口

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

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

type ReadWriter interface {
    Reader
    Writer
}

Released under the MIT License.