ZetCode

Go 接口

最后修改于 2025 年 5 月 3 日

在本文中,我们将展示如何在 Golang 中使用接口。

Go 中的接口是一种特定类型,它定义了一组函数签名,但不指定它们的实现。与 Java 或 C# 等需要显式声明接口的语言不同,Go 遵循隐式方法:一个类型只需实现其函数即可满足接口。

接口的主要作用是通过函数签名定义行为。每个函数签名都指定了名称、输入参数和返回类型,但实际实现留给了满足接口的类型。这种设计促进了灵活性和解耦,使 Go 代码更具模块化和适应性。

接口通常被称为暴露的 API 或契约。如果一个类型实现了接口中定义的函数(例如可排序接口),它就遵守了契约,保证该类型支持排序操作。这使得开发人员无需了解具体的底层类型即可编写通用且可重用的代码。

由于 Go 中的接口指定行为而不是实现,它们在实现多态性方面起着至关重要的作用。它们允许在期望给定接口的函数或数据结构中使用不同的类型,从而提高代码的可扩展性和可维护性。

使用接口可以使代码更清晰、更简洁、更易读。

Go 接口示例

下面的示例使用一个简单的 Shape 接口。

geo_shapes.go
package main

import (
    "fmt"
    "math"
)

type Shape interface {
    Area() float64
}

type Rectangle struct {
    Width, Height float64
}

type Circle struct {
    Radius float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func getArea(shape Shape) {

    fmt.Println(shape.Area())
}

func main() {

    r := Rectangle{Width: 7, Height: 8}
    c := Circle{Radius: 5}

    getArea(r)
    getArea(c)
}

Shape 是一个通用的几何形状。它无法被绘制。RectangleCircle 是特定的几何形状,可以被绘制,并且我们可以计算它们的面积。

type Shape interface {
    Area() float64
}

我们定义了 Shape 接口。它有一个函数签名:Area

type Rectangle struct {
    Width, Height float64
}

type Circle struct {
    Radius float64
}

我们定义了两种类型:RectangleCircle

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

我们为 RectangleCircle 定义了 Area 函数;我们说这两种类型实现了 Shape 接口。

func getArea(shape Shape) {

    fmt.Println(shape.Area())
}

getArea 函数将 Shape 接口作为参数。我们可以传递 RectangleCircle,因为它们都是形状。

$ go run geo_shapes.go 
56
78.53981633974483

Go 接口切片

在下面的示例中,我们创建了一个 Animal 接口的切片。

interface_slice.go
package main

import (
    "fmt"
)

type Animal interface {
    Sound() string
}

type Dog struct {
}

func (d Dog) Sound() string {
    return "Woof!"
}

type Cat struct {
}

func (c Cat) Sound() string {
    return "Meow!"
}

type Cow struct {
}

func (l Cow) Sound() string {
    return "Moo!"
}

func main() {

    animals := []Animal{Dog{}, Cat{}, Cow{}}

    for _, animal := range animals {

        fmt.Println(animal.Sound())
    }
}

该示例定义了一个 Animal 接口以及 DogCatCow 类型。

type Animal interface {
    Sound() string
}

type Dog struct {
}

func (d Dog) Sound() string {
    return "Woof!"
}

Dog 类型实现了 Animal 接口的 Sound 契约函数。

animals := []Animal{Dog{}, Cat{}, Cow{}}

for _, animal := range animals {

    fmt.Println(animal.Sound())
}

由于这三种类型都实现了一个共同的接口,我们可以将它们放入一个切片中。

$ go run interface_slice.go 
Woof!
Meow!
Moo!

Go Stringer 接口

Stringer 接口在 fmt 包中定义。当类型被传递给任何打印函数时,都会调用其 String 函数。我们可以自定义自己类型的输出消息。

type Stringer interface {
    String() string
}

这就是 Stringer 接口。

stringer.go
package main

import (
    "fmt"
)

type User struct {
    Name       string
    Occupation string
}

func (u User) String() string {

    return fmt.Sprintf("%s is a(n) %s", u.Name, u.Occupation)
}

func main() {

    u1 := User{"John Doe", "gardener"}
    u2 := User{"Roger Roe", "driver"}

    fmt.Println(u1)
    fmt.Println(u2)
}

在代码示例中,我们为 User 类型定义了 Stringer 接口的 String 函数。

func (u User) String() string {

    return fmt.Sprintf("%s is a(n) %s", u.Name, u.Occupation)
}

该实现返回一个字符串,指示用户的姓名和职业。

$ go run stringer.go 
John Doe is a(n) gardener
Roger Roe is a(n) driver

Go interface{}

Go 的 interface{} 是一个空接口;Go 中的所有类型都满足空接口。任何类型都可以赋值给用空接口声明的变量。

empty_interface.go
package main

import (
    "fmt"
)

type Body struct {
    Msg interface{}
}

func main() {

    b := Body{"Hello there"}
    fmt.Printf("%#v %T\n", b.Msg, b.Msg)

    b.Msg = 5
    fmt.Printf("%#v %T\n", b.Msg, b.Msg)
}

我们有一个类型为 interface{}Msg 变量。在示例中,我们将一个字符串和一个整数赋值给该变量。

$ go run empty_interface.go 
"Hello there" string 
5 int

Go 类型断言

类型断言 x.(T) 断言存储在 x 中的具体值是 T 类型,并且 x 不是 nil

type_assertion.go
package main

import (
    "fmt"
)

func main() {

    var val interface{} = "falcon"

    r, ok := val.(string)
    fmt.Println(r, ok)

    r2, ok2 := val.(int)
    fmt.Println(r2, ok2)

    r3 := val.(string)
    fmt.Println(r3)

    //r4 := val.(int)
    //fmt.Println(r4)
}

在代码示例中,我们检查 val 变量的类型。

r, ok := val.(string)
fmt.Println(r, ok)

r 变量中,我们有该值。ok 是一个布尔值,表示该值是否为字符串类型。

//r4 := val.(int)
//fmt.Println(r4)

注释掉的行会导致 panic:interface conversion: interface {} is string, not int

$ go run type_assertion.go 
falcon true
0 false
falcon

在下面的示例中,我们有一个键为 string 类型、值为 interface{} 的 map。它允许我们将各种类型用于值。

main.go
package main

import (
    "fmt"
    "log"
)

func main() {

    user := make(map[string]interface{}, 0)

    user["name"] = "John Doe"
    user["age"] = 21
    user["weight"] = 70.3

    age, ok := user["age"].(int)

    if !ok {
        log.Fatal("assert failed")
    }

    user["age"] = age + 1

    fmt.Printf("%+v", user)
}

由于值被定义为空接口,age 的底层类型丢失了。我们需要将值转换为 int 才能对其进行增量操作。

Go 类型开关

类型开关用于将接口的具体类型与 case 语句中提供的多个类型进行比较。

type_switch.go
package main

import "fmt"

type User struct {
    Name string
}

func checkType(a interface{}) {

    switch a.(type) {

    case int:
        fmt.Println("Type: int, Value:", a.(int))
    case string:
        fmt.Println("Type: string, Value:", a.(string))
    case float64:
        fmt.Println("Type: float64, Value:", a.(float64))
    case User:
        fmt.Println("Type: User, Value:", a.(User))
    default:
        fmt.Println("unknown type")
    }
}

func main() {

    checkType(4)
    checkType("falcon")
    checkType(User{"John Doe"})
    checkType(7.9)
    checkType(true)
}

checkType 函数在 switch 语句中确定其参数的类型。

$ go run type_switch.go 
Type: int, Value: 4
Type: string, Value: falcon
Type: User, Value: {John Doe}
Type: float64, Value: 7.9
unknown type

来源

The Go Programming Language Specification

在本文中,我们介绍了 Golang 中的接口类型。

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程